find_package(PythonInterp 3.7 REQUIRED)
endif()
-#-----------------------------------------------------------------------------
-# PHP
-#-----------------------------------------------------------------------------
-
-# Setting PHP binary variable as to command line (prevailing) or auto detect
-
-if (BUILD_API)
- if (NOT PHP_BIN)
- find_program (PHP_BIN php)
- endif()
- # sanity check if PHP binary exists
- if (NOT EXISTS ${PHP_BIN})
- message(WARNING "PHP binary not found. Only Python frontend can be used.")
- set(PHP_BIN "")
- else()
- message (STATUS "Using PHP binary " ${PHP_BIN})
- endif()
-endif()
-
#-----------------------------------------------------------------------------
# import scripts and utilities (importer only)
#-----------------------------------------------------------------------------
find_program(PYTHON_BEHAVE behave)
find_program(PYLINT NAMES pylint3 pylint)
find_program(PYTEST NAMES pytest py.test-3 py.test)
- find_program(PHPCS phpcs)
- find_program(PHPUNIT phpunit)
if (PYTHON_BEHAVE)
message(STATUS "Using Python behave binary ${PYTHON_BEHAVE}")
message(WARNING "behave not found. BDD tests disabled." )
endif()
- if (PHPUNIT)
- message(STATUS "Using phpunit binary ${PHPUNIT}")
- add_test(NAME php
- COMMAND ${PHPUNIT} ./
- WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/test/php)
- else()
- message(WARNING "phpunit not found. PHP unit tests disabled." )
- endif()
-
- if (PHPCS)
- message(STATUS "Using phpcs binary ${PHPCS}")
- add_test(NAME phpcs
- COMMAND ${PHPCS} --report-width=120 --colors lib-php
- WORKING_DIRECTORY ${PROJECT_SOURCE_DIR})
- else()
- message(WARNING "phpcs not found. PHP linting tests disabled." )
- endif()
-
if (PYLINT)
message(STATUS "Using pylint binary ${PYLINT}")
add_test(NAME pylint
DESTINATION ${CMAKE_INSTALL_BINDIR}
RENAME nominatim)
- if (EXISTS ${PHP_BIN})
- configure_file(${PROJECT_SOURCE_DIR}/cmake/paths-py.tmpl paths-py.installed)
- else()
- configure_file(${PROJECT_SOURCE_DIR}/cmake/paths-py-no-php.tmpl paths-py.installed)
- endif()
+ configure_file(${PROJECT_SOURCE_DIR}/cmake/paths-py-no-php.tmpl paths-py.installed)
foreach (submodule nominatim_db nominatim_api)
install(DIRECTORY src/${submodule}
DESTINATION ${NOMINATIM_LIBDIR}/module)
endif()
-if (BUILD_API AND EXISTS ${PHP_BIN})
- install(DIRECTORY lib-php DESTINATION ${NOMINATIM_LIBDIR})
-endif()
-
install(FILES settings/env.defaults
settings/address-levels.json
settings/phrase-settings.json
+++ /dev/null
-# SPDX-License-Identifier: GPL-2.0-only
-#
-# This file is part of Nominatim. (https://nominatim.org)
-#
-# Copyright (C) 2022 by the Nominatim developer community.
-# For a full list of authors see the git log.
-"""
-Path settings for extra data used by Nominatim (installed version).
-"""
-from pathlib import Path
-
-PHPLIB_DIR = (Path('@NOMINATIM_LIBDIR@') / 'lib-php').resolve()
-SQLLIB_DIR = (Path('@NOMINATIM_LIBDIR@') / 'lib-sql').resolve()
-DATA_DIR = Path('@NOMINATIM_DATADIR@').resolve()
-CONFIG_DIR = Path('@NOMINATIM_CONFIGDIR@').resolve()
+++ /dev/null
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-namespace Nominatim;
-
-require_once(CONST_LibDir.'/ClassTypes.php');
-
-/**
- * Detailed list of address parts for a single result
- */
-class AddressDetails
-{
- private $iPlaceID;
- private $aAddressLines;
-
- public function __construct(&$oDB, $iPlaceID, $sHousenumber, $mLangPref)
- {
- $this->iPlaceID = $iPlaceID;
-
- if (is_array($mLangPref)) {
- $mLangPref = $oDB->getArraySQL($oDB->getDBQuotedList($mLangPref));
- }
-
- if (!isset($sHousenumber)) {
- $sHousenumber = -1;
- }
-
- $sSQL = 'SELECT *,';
- $sSQL .= ' get_name_by_language(name,'.$mLangPref.') as localname';
- $sSQL .= ' FROM get_addressdata('.$iPlaceID.','.$sHousenumber.')';
- $sSQL .= ' ORDER BY rank_address DESC, isaddress DESC';
-
- $this->aAddressLines = $oDB->getAll($sSQL);
- }
-
- private static function isAddress($aLine)
- {
- return $aLine['isaddress'] || $aLine['type'] == 'country_code';
- }
-
- public function getAddressDetails($bAll = false)
- {
- if ($bAll) {
- return $this->aAddressLines;
- }
-
- return array_filter($this->aAddressLines, array(__CLASS__, 'isAddress'));
- }
-
- public function getLocaleAddress()
- {
- $aParts = array();
- $sPrevResult = '';
-
- foreach ($this->aAddressLines as $aLine) {
- if ($aLine['isaddress'] && $sPrevResult != $aLine['localname']) {
- $sPrevResult = $aLine['localname'];
- $aParts[] = $sPrevResult;
- }
- }
-
- return join(', ', $aParts);
- }
-
- public function getAddressNames()
- {
- $aAddress = array();
-
- foreach ($this->aAddressLines as $aLine) {
- if (!self::isAddress($aLine)) {
- continue;
- }
-
- $sTypeLabel = ClassTypes\getLabelTag($aLine);
-
- $sName = null;
- if (isset($aLine['localname']) && $aLine['localname']!=='') {
- $sName = $aLine['localname'];
- } elseif (isset($aLine['housenumber']) && $aLine['housenumber']!=='') {
- $sName = $aLine['housenumber'];
- }
-
- if (isset($sName)
- && (!isset($aAddress[$sTypeLabel])
- || $aLine['class'] == 'place')
- ) {
- $aAddress[$sTypeLabel] = $sName;
-
- if (!empty($aLine['name'])) {
- $this->addSubdivisionCode($aAddress, $aLine['admin_level'], $aLine['name']);
- }
- }
- }
-
- return $aAddress;
- }
-
- /**
- * Annotates the given json with geocodejson address information fields.
- *
- * @param array $aJson Json hash to add the fields to.
- *
- * Geocodejson has the following fields:
- * street, locality, postcode, city, district,
- * county, state, country
- *
- * Postcode and housenumber are added by type, district is not used.
- * All other fields are set according to address rank.
- */
- public function addGeocodeJsonAddressParts(&$aJson)
- {
- foreach (array_reverse($this->aAddressLines) as $aLine) {
- if (!$aLine['isaddress']) {
- continue;
- }
-
- if (!isset($aLine['localname']) || $aLine['localname'] == '') {
- continue;
- }
-
- if ($aLine['type'] == 'postcode' || $aLine['type'] == 'postal_code') {
- $aJson['postcode'] = $aLine['localname'];
- continue;
- }
-
- if ($aLine['type'] == 'house_number') {
- $aJson['housenumber'] = $aLine['localname'];
- continue;
- }
-
- if ($this->iPlaceID == $aLine['place_id']) {
- continue;
- }
-
- $iRank = (int)$aLine['rank_address'];
-
- if ($iRank > 25 && $iRank < 28) {
- $aJson['street'] = $aLine['localname'];
- } elseif ($iRank >= 22 && $iRank <= 25) {
- $aJson['locality'] = $aLine['localname'];
- } elseif ($iRank >= 17 && $iRank <= 21) {
- $aJson['district'] = $aLine['localname'];
- } elseif ($iRank >= 13 && $iRank <= 16) {
- $aJson['city'] = $aLine['localname'];
- } elseif ($iRank >= 10 && $iRank <= 12) {
- $aJson['county'] = $aLine['localname'];
- } elseif ($iRank >= 5 && $iRank <= 9) {
- $aJson['state'] = $aLine['localname'];
- } elseif ($iRank == 4) {
- $aJson['country'] = $aLine['localname'];
- }
- }
- }
-
- public function getAdminLevels()
- {
- $aAddress = array();
- foreach (array_reverse($this->aAddressLines) as $aLine) {
- if (self::isAddress($aLine)
- && isset($aLine['admin_level'])
- && $aLine['admin_level'] < 15
- && !isset($aAddress['level'.$aLine['admin_level']])
- ) {
- $aAddress['level'.$aLine['admin_level']] = $aLine['localname'];
- }
- }
- return $aAddress;
- }
-
- public function debugInfo()
- {
- return $this->aAddressLines;
- }
-
- private function addSubdivisionCode(&$aAddress, $iAdminLevel, $nameDetails)
- {
- if (is_string($nameDetails)) {
- $nameDetails = json_decode('{' . str_replace('"=>"', '":"', $nameDetails) . '}', true);
- }
- if (!empty($nameDetails['ISO3166-2'])) {
- $aAddress["ISO3166-2-lvl$iAdminLevel"] = $nameDetails['ISO3166-2'];
- }
- }
-}
+++ /dev/null
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-namespace Nominatim\ClassTypes;
-
-/**
- * Create a label tag for the given place that can be used as an XML name.
- *
- * @param array[] $aPlace Information about the place to label.
- *
- * A label tag groups various object types together under a common
- * label. The returned value is lower case and has no spaces
- */
-function getLabelTag($aPlace, $sCountry = null)
-{
- $iRank = (int) ($aPlace['rank_address'] ?? 30);
- $sLabel;
- if (isset($aPlace['place_type'])) {
- $sLabel = $aPlace['place_type'];
- } elseif ($aPlace['class'] == 'boundary' && $aPlace['type'] == 'administrative') {
- $sLabel = getBoundaryLabel($iRank/2, $sCountry);
- } elseif ($aPlace['type'] == 'postal_code') {
- $sLabel = 'postcode';
- } elseif ($iRank < 26) {
- $sLabel = $aPlace['type'];
- } elseif ($iRank < 28) {
- $sLabel = 'road';
- } elseif ($aPlace['class'] == 'place'
- && ($aPlace['type'] == 'house_number' ||
- $aPlace['type'] == 'house_name' ||
- $aPlace['type'] == 'country_code')
- ) {
- $sLabel = $aPlace['type'];
- } else {
- $sLabel = $aPlace['class'];
- }
-
- return strtolower(str_replace(' ', '_', $sLabel));
-}
-
-/**
- * Create a label for the given place.
- *
- * @param array[] $aPlace Information about the place to label.
- */
-function getLabel($aPlace, $sCountry = null)
-{
- if (isset($aPlace['place_type'])) {
- return ucwords(str_replace('_', ' ', $aPlace['place_type']));
- }
-
- if ($aPlace['class'] == 'boundary' && $aPlace['type'] == 'administrative') {
- return getBoundaryLabel(($aPlace['rank_address'] ?? 30)/2, $sCountry ?? null);
- }
-
- // Return a label only for 'important' class/type combinations
- if (getImportance($aPlace) !== null) {
- return ucwords(str_replace('_', ' ', $aPlace['type']));
- }
-
- return null;
-}
-
-
-/**
- * Return a simple label for an administrative boundary for the given country.
- *
- * @param int $iAdminLevel Content of admin_level tag.
- * @param string $sCountry Country code of the country where the object is
- * in. May be null, in which case a world-wide
- * fallback is used.
- * @param string $sFallback String to return if no explicit string is listed.
- *
- * @return string
- */
-function getBoundaryLabel($iAdminLevel, $sCountry, $sFallback = 'Administrative')
-{
- static $aBoundaryList = array (
- 'default' => array (
- 1 => 'Continent',
- 2 => 'Country',
- 3 => 'Region',
- 4 => 'State',
- 5 => 'State District',
- 6 => 'County',
- 7 => 'Municipality',
- 8 => 'City',
- 9 => 'City District',
- 10 => 'Suburb',
- 11 => 'Neighbourhood',
- 12 => 'City Block'
- ),
- 'no' => array (
- 3 => 'State',
- 4 => 'County'
- ),
- 'se' => array (
- 3 => 'State',
- 4 => 'County'
- )
- );
-
- if (isset($aBoundaryList[$sCountry])
- && isset($aBoundaryList[$sCountry][$iAdminLevel])
- ) {
- return $aBoundaryList[$sCountry][$iAdminLevel];
- }
-
- return $aBoundaryList['default'][$iAdminLevel] ?? $sFallback;
-}
-
-/**
- * Return an estimated radius of how far the object node extends.
- *
- * @param array[] $aPlace Information about the place. This must be a node
- * feature.
- *
- * @return float The radius around the feature in degrees.
- */
-function getDefRadius($aPlace)
-{
- $aSpecialRadius = array(
- 'place:continent' => 25,
- 'place:country' => 7,
- 'place:state' => 2.6,
- 'place:province' => 2.6,
- 'place:region' => 1.0,
- 'place:county' => 0.7,
- 'place:city' => 0.16,
- 'place:municipality' => 0.16,
- 'place:island' => 0.32,
- 'place:postcode' => 0.16,
- 'place:town' => 0.04,
- 'place:village' => 0.02,
- 'place:hamlet' => 0.02,
- 'place:district' => 0.02,
- 'place:borough' => 0.02,
- 'place:suburb' => 0.02,
- 'place:locality' => 0.01,
- 'place:neighbourhood'=> 0.01,
- 'place:quarter' => 0.01,
- 'place:city_block' => 0.01,
- 'landuse:farm' => 0.01,
- 'place:farm' => 0.01,
- 'place:airport' => 0.015,
- 'aeroway:aerodrome' => 0.015,
- 'railway:station' => 0.005
- );
-
- $sClassPlace = $aPlace['class'].':'.$aPlace['type'];
-
- return $aSpecialRadius[$sClassPlace] ?? 0.00005;
-}
-
-/**
- * Get the icon to use with the given object.
- */
-function getIcon($aPlace)
-{
- $aIcons = array(
- 'boundary:administrative' => 'poi_boundary_administrative',
- 'place:city' => 'poi_place_city',
- 'place:town' => 'poi_place_town',
- 'place:village' => 'poi_place_village',
- 'place:hamlet' => 'poi_place_village',
- 'place:suburb' => 'poi_place_village',
- 'place:locality' => 'poi_place_village',
- 'place:airport' => 'transport_airport2',
- 'aeroway:aerodrome' => 'transport_airport2',
- 'railway:station' => 'transport_train_station2',
- 'amenity:place_of_worship' => 'place_of_worship_unknown3',
- 'amenity:pub' => 'food_pub',
- 'amenity:bar' => 'food_bar',
- 'amenity:university' => 'education_university',
- 'tourism:museum' => 'tourist_museum',
- 'amenity:arts_centre' => 'tourist_art_gallery2',
- 'tourism:zoo' => 'tourist_zoo',
- 'tourism:theme_park' => 'poi_point_of_interest',
- 'tourism:attraction' => 'poi_point_of_interest',
- 'leisure:golf_course' => 'sport_golf',
- 'historic:castle' => 'tourist_castle',
- 'amenity:hospital' => 'health_hospital',
- 'amenity:school' => 'education_school',
- 'amenity:theatre' => 'tourist_theatre',
- 'amenity:library' => 'amenity_library',
- 'amenity:fire_station' => 'amenity_firestation3',
- 'amenity:police' => 'amenity_police2',
- 'amenity:bank' => 'money_bank2',
- 'amenity:post_office' => 'amenity_post_office',
- 'tourism:hotel' => 'accommodation_hotel2',
- 'amenity:cinema' => 'tourist_cinema',
- 'tourism:artwork' => 'tourist_art_gallery2',
- 'historic:archaeological_site' => 'tourist_archaeological2',
- 'amenity:doctors' => 'health_doctors',
- 'leisure:sports_centre' => 'sport_leisure_centre',
- 'leisure:swimming_pool' => 'sport_swimming_outdoor',
- 'shop:supermarket' => 'shopping_supermarket',
- 'shop:convenience' => 'shopping_convenience',
- 'amenity:restaurant' => 'food_restaurant',
- 'amenity:fast_food' => 'food_fastfood',
- 'amenity:cafe' => 'food_cafe',
- 'tourism:guest_house' => 'accommodation_bed_and_breakfast',
- 'amenity:pharmacy' => 'health_pharmacy_dispensing',
- 'amenity:fuel' => 'transport_fuel',
- 'natural:peak' => 'poi_peak',
- 'natural:wood' => 'landuse_coniferous_and_deciduous',
- 'shop:bicycle' => 'shopping_bicycle',
- 'shop:clothes' => 'shopping_clothes',
- 'shop:hairdresser' => 'shopping_hairdresser',
- 'shop:doityourself' => 'shopping_diy',
- 'shop:estate_agent' => 'shopping_estateagent2',
- 'shop:car' => 'shopping_car',
- 'shop:garden_centre' => 'shopping_garden_centre',
- 'shop:car_repair' => 'shopping_car_repair',
- 'shop:bakery' => 'shopping_bakery',
- 'shop:butcher' => 'shopping_butcher',
- 'shop:apparel' => 'shopping_clothes',
- 'shop:laundry' => 'shopping_laundrette',
- 'shop:beverages' => 'shopping_alcohol',
- 'shop:alcohol' => 'shopping_alcohol',
- 'shop:optician' => 'health_opticians',
- 'shop:chemist' => 'health_pharmacy',
- 'shop:gallery' => 'tourist_art_gallery2',
- 'shop:jewelry' => 'shopping_jewelry',
- 'tourism:information' => 'amenity_information',
- 'historic:ruins' => 'tourist_ruin',
- 'amenity:college' => 'education_school',
- 'historic:monument' => 'tourist_monument',
- 'historic:memorial' => 'tourist_monument',
- 'historic:mine' => 'poi_mine',
- 'tourism:caravan_site' => 'accommodation_caravan_park',
- 'amenity:bus_station' => 'transport_bus_station',
- 'amenity:atm' => 'money_atm2',
- 'tourism:viewpoint' => 'tourist_view_point',
- 'tourism:guesthouse' => 'accommodation_bed_and_breakfast',
- 'railway:tram' => 'transport_tram_stop',
- 'amenity:courthouse' => 'amenity_court',
- 'amenity:recycling' => 'amenity_recycling',
- 'amenity:dentist' => 'health_dentist',
- 'natural:beach' => 'tourist_beach',
- 'railway:tram_stop' => 'transport_tram_stop',
- 'amenity:prison' => 'amenity_prison',
- 'highway:bus_stop' => 'transport_bus_stop2'
- );
-
- $sClassPlace = $aPlace['class'].':'.$aPlace['type'];
-
- return $aIcons[$sClassPlace] ?? null;
-}
-
-/**
- * Get an icon for the given object with its full URL.
- */
-function getIconFile($aPlace)
-{
- if (CONST_MapIcon_URL === false) {
- return null;
- }
-
- $sIcon = getIcon($aPlace);
-
- if (!isset($sIcon)) {
- return null;
- }
-
- return CONST_MapIcon_URL.'/'.$sIcon.'.p.20.png';
-}
-
-/**
- * Return a class importance value for the given place.
- *
- * @param array[] $aPlace Information about the place.
- *
- * @return int An importance value. The lower the value, the more
- * important the class.
- */
-function getImportance($aPlace)
-{
- static $aWithImportance = null;
-
- if ($aWithImportance === null) {
- $aWithImportance = array_flip(array(
- 'boundary:administrative',
- 'place:country',
- 'place:state',
- 'place:province',
- 'place:county',
- 'place:city',
- 'place:region',
- 'place:island',
- 'place:town',
- 'place:village',
- 'place:hamlet',
- 'place:suburb',
- 'place:locality',
- 'landuse:farm',
- 'place:farm',
- 'highway:motorway_junction',
- 'highway:motorway',
- 'highway:trunk',
- 'highway:primary',
- 'highway:secondary',
- 'highway:tertiary',
- 'highway:residential',
- 'highway:unclassified',
- 'highway:living_street',
- 'highway:service',
- 'highway:track',
- 'highway:road',
- 'highway:byway',
- 'highway:bridleway',
- 'highway:cycleway',
- 'highway:pedestrian',
- 'highway:footway',
- 'highway:steps',
- 'highway:motorway_link',
- 'highway:trunk_link',
- 'highway:primary_link',
- 'landuse:industrial',
- 'landuse:residential',
- 'landuse:retail',
- 'landuse:commercial',
- 'place:airport',
- 'aeroway:aerodrome',
- 'railway:station',
- 'amenity:place_of_worship',
- 'amenity:pub',
- 'amenity:bar',
- 'amenity:university',
- 'tourism:museum',
- 'amenity:arts_centre',
- 'tourism:zoo',
- 'tourism:theme_park',
- 'tourism:attraction',
- 'leisure:golf_course',
- 'historic:castle',
- 'amenity:hospital',
- 'amenity:school',
- 'amenity:theatre',
- 'amenity:public_building',
- 'amenity:library',
- 'amenity:townhall',
- 'amenity:community_centre',
- 'amenity:fire_station',
- 'amenity:police',
- 'amenity:bank',
- 'amenity:post_office',
- 'leisure:park',
- 'amenity:park',
- 'landuse:park',
- 'landuse:recreation_ground',
- 'tourism:hotel',
- 'tourism:motel',
- 'amenity:cinema',
- 'tourism:artwork',
- 'historic:archaeological_site',
- 'amenity:doctors',
- 'leisure:sports_centre',
- 'leisure:swimming_pool',
- 'shop:supermarket',
- 'shop:convenience',
- 'amenity:restaurant',
- 'amenity:fast_food',
- 'amenity:cafe',
- 'tourism:guest_house',
- 'amenity:pharmacy',
- 'amenity:fuel',
- 'natural:peak',
- 'waterway:waterfall',
- 'natural:wood',
- 'natural:water',
- 'landuse:forest',
- 'landuse:cemetery',
- 'landuse:allotments',
- 'landuse:farmyard',
- 'railway:rail',
- 'waterway:canal',
- 'waterway:river',
- 'waterway:stream',
- 'shop:bicycle',
- 'shop:clothes',
- 'shop:hairdresser',
- 'shop:doityourself',
- 'shop:estate_agent',
- 'shop:car',
- 'shop:garden_centre',
- 'shop:car_repair',
- 'shop:newsagent',
- 'shop:bakery',
- 'shop:furniture',
- 'shop:butcher',
- 'shop:apparel',
- 'shop:electronics',
- 'shop:department_store',
- 'shop:books',
- 'shop:yes',
- 'shop:outdoor',
- 'shop:mall',
- 'shop:florist',
- 'shop:charity',
- 'shop:hardware',
- 'shop:laundry',
- 'shop:shoes',
- 'shop:beverages',
- 'shop:dry_cleaning',
- 'shop:carpet',
- 'shop:computer',
- 'shop:alcohol',
- 'shop:optician',
- 'shop:chemist',
- 'shop:gallery',
- 'shop:mobile_phone',
- 'shop:sports',
- 'shop:jewelry',
- 'shop:pet',
- 'shop:beauty',
- 'shop:stationery',
- 'shop:shopping_centre',
- 'shop:general',
- 'shop:electrical',
- 'shop:toys',
- 'shop:jeweller',
- 'shop:betting',
- 'shop:household',
- 'shop:travel_agency',
- 'shop:hifi',
- 'amenity:shop',
- 'tourism:information',
- 'place:house',
- 'place:house_name',
- 'place:house_number',
- 'place:country_code',
- 'leisure:pitch',
- 'highway:unsurfaced',
- 'historic:ruins',
- 'amenity:college',
- 'historic:monument',
- 'railway:subway',
- 'historic:memorial',
- 'leisure:nature_reserve',
- 'leisure:common',
- 'waterway:lock_gate',
- 'natural:fell',
- 'amenity:nightclub',
- 'highway:path',
- 'leisure:garden',
- 'landuse:reservoir',
- 'leisure:playground',
- 'leisure:stadium',
- 'historic:mine',
- 'natural:cliff',
- 'tourism:caravan_site',
- 'amenity:bus_station',
- 'amenity:kindergarten',
- 'highway:construction',
- 'amenity:atm',
- 'amenity:emergency_phone',
- 'waterway:lock',
- 'waterway:riverbank',
- 'natural:coastline',
- 'tourism:viewpoint',
- 'tourism:hostel',
- 'tourism:bed_and_breakfast',
- 'railway:halt',
- 'railway:platform',
- 'railway:tram',
- 'amenity:courthouse',
- 'amenity:recycling',
- 'amenity:dentist',
- 'natural:beach',
- 'place:moor',
- 'amenity:grave_yard',
- 'waterway:drain',
- 'landuse:grass',
- 'landuse:village_green',
- 'natural:bay',
- 'railway:tram_stop',
- 'leisure:marina',
- 'highway:stile',
- 'natural:moor',
- 'railway:light_rail',
- 'railway:narrow_gauge',
- 'natural:land',
- 'amenity:village_hall',
- 'waterway:dock',
- 'amenity:veterinary',
- 'landuse:brownfield',
- 'leisure:track',
- 'railway:historic_station',
- 'landuse:construction',
- 'amenity:prison',
- 'landuse:quarry',
- 'amenity:telephone',
- 'highway:traffic_signals',
- 'natural:heath',
- 'historic:house',
- 'amenity:social_club',
- 'landuse:military',
- 'amenity:health_centre',
- 'historic:building',
- 'amenity:clinic',
- 'highway:services',
- 'amenity:ferry_terminal',
- 'natural:marsh',
- 'natural:hill',
- 'highway:raceway',
- 'amenity:taxi',
- 'amenity:take_away',
- 'amenity:car_rental',
- 'place:islet',
- 'amenity:nursery',
- 'amenity:nursing_home',
- 'amenity:toilets',
- 'amenity:hall',
- 'waterway:boatyard',
- 'highway:mini_roundabout',
- 'historic:manor',
- 'tourism:chalet',
- 'amenity:bicycle_parking',
- 'amenity:hotel',
- 'waterway:weir',
- 'natural:wetland',
- 'natural:cave_entrance',
- 'amenity:crematorium',
- 'tourism:picnic_site',
- 'landuse:wood',
- 'landuse:basin',
- 'natural:tree',
- 'leisure:slipway',
- 'landuse:meadow',
- 'landuse:piste',
- 'amenity:care_home',
- 'amenity:club',
- 'amenity:medical_centre',
- 'historic:roman_road',
- 'historic:fort',
- 'railway:subway_entrance',
- 'historic:yes',
- 'highway:gate',
- 'leisure:fishing',
- 'historic:museum',
- 'amenity:car_wash',
- 'railway:level_crossing',
- 'leisure:bird_hide',
- 'natural:headland',
- 'tourism:apartments',
- 'amenity:shopping',
- 'natural:scrub',
- 'natural:fen',
- 'building:yes',
- 'mountain_pass:yes',
- 'amenity:parking',
- 'highway:bus_stop',
- 'place:postcode',
- 'amenity:post_box',
- 'place:houses',
- 'railway:preserved',
- 'waterway:derelict_canal',
- 'amenity:dead_pub',
- 'railway:disused_station',
- 'railway:abandoned',
- 'railway:disused'
- ));
- }
-
- $sClassPlace = $aPlace['class'].':'.$aPlace['type'];
-
- return $aWithImportance[$sClassPlace] ?? null;
-}
+++ /dev/null
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-namespace Nominatim;
-
-require_once(CONST_LibDir.'/DatabaseError.php');
-
-/**
- * Uses PDO to access the database specified in the CONST_Database_DSN
- * setting.
- */
-class DB
-{
- protected $connection;
-
- public function __construct($sDSN = null)
- {
- $this->sDSN = $sDSN ?? getSetting('DATABASE_DSN');
- }
-
- public function connect($bNew = false, $bPersistent = true)
- {
- if (isset($this->connection) && !$bNew) {
- return true;
- }
- $aConnOptions = array(
- \PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION,
- \PDO::ATTR_DEFAULT_FETCH_MODE => \PDO::FETCH_ASSOC,
- \PDO::ATTR_PERSISTENT => $bPersistent
- );
-
- // https://secure.php.net/manual/en/ref.pdo-pgsql.connection.php
- try {
- $this->connection = new \PDO($this->sDSN, null, null, $aConnOptions);
- } catch (\PDOException $e) {
- $sMsg = 'Failed to establish database connection:' . $e->getMessage();
- throw new \Nominatim\DatabaseError($sMsg, 500, null, $e->getMessage());
- }
-
- $this->connection->exec("SET DateStyle TO 'sql,european'");
- $this->connection->exec("SET client_encoding TO 'utf-8'");
- // Disable JIT and parallel workers. They interfere badly with search SQL.
- $this->connection->exec('SET max_parallel_workers_per_gather TO 0');
- if ($this->getPostgresVersion() >= 11) {
- $this->connection->exec('SET jit_above_cost TO -1');
- }
-
- $iMaxExecution = ini_get('max_execution_time');
- if ($iMaxExecution > 0) {
- $this->connection->setAttribute(\PDO::ATTR_TIMEOUT, $iMaxExecution); // seconds
- }
-
- return true;
- }
-
- // returns the number of rows that were modified or deleted by the SQL
- // statement. If no rows were affected returns 0.
- public function exec($sSQL, $aInputVars = null, $sErrMessage = 'Database query failed')
- {
- $val = null;
- try {
- if (isset($aInputVars)) {
- $stmt = $this->connection->prepare($sSQL);
- $stmt->execute($aInputVars);
- } else {
- $val = $this->connection->exec($sSQL);
- }
- } catch (\PDOException $e) {
- throw new \Nominatim\DatabaseError($sErrMessage, 500, null, $e, $sSQL);
- }
- return $val;
- }
-
- /**
- * Executes query. Returns first row as array.
- * Returns false if no result found.
- *
- * @param string $sSQL
- *
- * @return array[]
- */
- public function getRow($sSQL, $aInputVars = null, $sErrMessage = 'Database query failed')
- {
- try {
- $stmt = $this->getQueryStatement($sSQL, $aInputVars, $sErrMessage);
- $row = $stmt->fetch();
- } catch (\PDOException $e) {
- throw new \Nominatim\DatabaseError($sErrMessage, 500, null, $e, $sSQL);
- }
- return $row;
- }
-
- /**
- * Executes query. Returns first value of first result.
- * Returns false if no results found.
- *
- * @param string $sSQL
- *
- * @return array[]
- */
- public function getOne($sSQL, $aInputVars = null, $sErrMessage = 'Database query failed')
- {
- try {
- $stmt = $this->getQueryStatement($sSQL, $aInputVars, $sErrMessage);
- $row = $stmt->fetch(\PDO::FETCH_NUM);
- if ($row === false) {
- return false;
- }
- } catch (\PDOException $e) {
- throw new \Nominatim\DatabaseError($sErrMessage, 500, null, $e, $sSQL);
- }
- return $row[0];
- }
-
- /**
- * Executes query. Returns array of results (arrays).
- * Returns empty array if no results found.
- *
- * @param string $sSQL
- *
- * @return array[]
- */
- public function getAll($sSQL, $aInputVars = null, $sErrMessage = 'Database query failed')
- {
- try {
- $stmt = $this->getQueryStatement($sSQL, $aInputVars, $sErrMessage);
- $rows = $stmt->fetchAll();
- } catch (\PDOException $e) {
- throw new \Nominatim\DatabaseError($sErrMessage, 500, null, $e, $sSQL);
- }
- return $rows;
- }
-
- /**
- * Executes query. Returns array of the first value of each result.
- * Returns empty array if no results found.
- *
- * @param string $sSQL
- *
- * @return array[]
- */
- public function getCol($sSQL, $aInputVars = null, $sErrMessage = 'Database query failed')
- {
- $aVals = array();
- try {
- $stmt = $this->getQueryStatement($sSQL, $aInputVars, $sErrMessage);
-
- while (($val = $stmt->fetchColumn(0)) !== false) { // returns first column or false
- $aVals[] = $val;
- }
- } catch (\PDOException $e) {
- throw new \Nominatim\DatabaseError($sErrMessage, 500, null, $e, $sSQL);
- }
- return $aVals;
- }
-
- /**
- * Executes query. Returns associate array mapping first value to second value of each result.
- * Returns empty array if no results found.
- *
- * @param string $sSQL
- *
- * @return array[]
- */
- public function getAssoc($sSQL, $aInputVars = null, $sErrMessage = 'Database query failed')
- {
- try {
- $stmt = $this->getQueryStatement($sSQL, $aInputVars, $sErrMessage);
-
- $aList = array();
- while ($aRow = $stmt->fetch(\PDO::FETCH_NUM)) {
- $aList[$aRow[0]] = $aRow[1];
- }
- } catch (\PDOException $e) {
- throw new \Nominatim\DatabaseError($sErrMessage, 500, null, $e, $sSQL);
- }
- return $aList;
- }
-
- /**
- * Executes query. Returns a PDO statement to iterate over.
- *
- * @param string $sSQL
- *
- * @return PDOStatement
- */
- public function getQueryStatement($sSQL, $aInputVars = null, $sErrMessage = 'Database query failed')
- {
- try {
- if (isset($aInputVars)) {
- $stmt = $this->connection->prepare($sSQL);
- $stmt->execute($aInputVars);
- } else {
- $stmt = $this->connection->query($sSQL);
- }
- } catch (\PDOException $e) {
- throw new \Nominatim\DatabaseError($sErrMessage, 500, null, $e, $sSQL);
- }
- return $stmt;
- }
-
- /**
- * St. John's Way => 'St. John\'s Way'
- *
- * @param string $sVal Text to be quoted.
- *
- * @return string
- */
- public function getDBQuoted($sVal)
- {
- return $this->connection->quote($sVal);
- }
-
- /**
- * Like getDBQuoted, but takes an array.
- *
- * @param array $aVals List of text to be quoted.
- *
- * @return array[]
- */
- public function getDBQuotedList($aVals)
- {
- return array_map(function ($sVal) {
- return $this->getDBQuoted($sVal);
- }, $aVals);
- }
-
- /**
- * [1,2,'b'] => 'ARRAY[1,2,'b']''
- *
- * @param array $aVals List of text to be quoted.
- *
- * @return string
- */
- public function getArraySQL($a)
- {
- return 'ARRAY['.join(',', $a).']';
- }
-
- /**
- * Check if a table exists in the database. Returns true if it does.
- *
- * @param string $sTableName
- *
- * @return boolean
- */
- public function tableExists($sTableName)
- {
- $sSQL = 'SELECT count(*) FROM pg_tables WHERE tablename = :tablename';
- return ($this->getOne($sSQL, array(':tablename' => $sTableName)) == 1);
- }
-
- /**
- * Deletes a table. Returns true if deleted or didn't exist.
- *
- * @param string $sTableName
- *
- * @return boolean
- */
- public function deleteTable($sTableName)
- {
- return $this->exec('DROP TABLE IF EXISTS '.$sTableName.' CASCADE') == 0;
- }
-
- /**
- * Tries to connect to the database but on failure doesn't throw an exception.
- *
- * @return boolean
- */
- public function checkConnection()
- {
- $bExists = true;
- try {
- $this->connect(true);
- } catch (\Nominatim\DatabaseError $e) {
- $bExists = false;
- }
- return $bExists;
- }
-
- /**
- * e.g. 9.6, 10, 11.2
- *
- * @return float
- */
- public function getPostgresVersion()
- {
- $sVersionString = $this->getOne('SHOW server_version_num');
- preg_match('#([0-9]?[0-9])([0-9][0-9])[0-9][0-9]#', $sVersionString, $aMatches);
- return (float) ($aMatches[1].'.'.$aMatches[2]);
- }
-
- /**
- * e.g. 2, 2.2
- *
- * @return float
- */
- public function getPostgisVersion()
- {
- $sVersionString = $this->getOne('select postgis_lib_version()');
- preg_match('#^([0-9]+)[.]([0-9]+)[.]#', $sVersionString, $aMatches);
- return (float) ($aMatches[1].'.'.$aMatches[2]);
- }
-
- /**
- * Returns an associate array of postgresql database connection settings. Keys can
- * be 'database', 'hostspec', 'port', 'username', 'password'.
- * Returns empty array on failure, thus check if at least 'database' is set.
- *
- * @return array[]
- */
- public static function parseDSN($sDSN)
- {
- // https://secure.php.net/manual/en/ref.pdo-pgsql.connection.php
- $aInfo = array();
- if (preg_match('/^pgsql:(.+)$/', $sDSN, $aMatches)) {
- foreach (explode(';', $aMatches[1]) as $sKeyVal) {
- list($sKey, $sVal) = explode('=', $sKeyVal, 2);
- if ($sKey == 'host') {
- $sKey = 'hostspec';
- } elseif ($sKey == 'dbname') {
- $sKey = 'database';
- } elseif ($sKey == 'user') {
- $sKey = 'username';
- }
- $aInfo[$sKey] = $sVal;
- }
- }
- return $aInfo;
- }
-
- /**
- * Takes an array of settings and return the DNS string. Key names can be
- * 'database', 'hostspec', 'port', 'username', 'password' but aliases
- * 'dbname', 'host' and 'user' are also supported.
- *
- * @return string
- *
- */
- public static function generateDSN($aInfo)
- {
- $sDSN = sprintf(
- 'pgsql:host=%s;port=%s;dbname=%s;user=%s;password=%s;',
- $aInfo['host'] ?? $aInfo['hostspec'] ?? '',
- $aInfo['port'] ?? '',
- $aInfo['dbname'] ?? $aInfo['database'] ?? '',
- $aInfo['user'] ?? '',
- $aInfo['password'] ?? ''
- );
- $sDSN = preg_replace('/\b\w+=;/', '', $sDSN);
- $sDSN = preg_replace('/;\Z/', '', $sDSN);
-
- return $sDSN;
- }
-}
+++ /dev/null
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-namespace Nominatim;
-
-class DatabaseError extends \Exception
-{
-
- public function __construct($message, $code, $previous, $oPDOErr, $sSql = null)
- {
- parent::__construct($message, $code, $previous);
- // https://secure.php.net/manual/en/class.pdoexception.php
- $this->oPDOErr = $oPDOErr;
- $this->sSql = $sSql;
- }
-
- public function __toString()
- {
- return __CLASS__ . ": [{$this->code}]: {$this->message}\n";
- }
-
- public function getSqlError()
- {
- return $this->oPDOErr->getMessage();
- }
-
- public function getSqlDebugDump()
- {
- if (CONST_Debug) {
- return var_export($this->oPDOErr, true);
- } else {
- return $this->sSql;
- }
- }
-}
+++ /dev/null
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-namespace Nominatim;
-
-class Debug
-{
- public static function newFunction($sHeading)
- {
- echo "<pre><h2>Debug output for $sHeading</h2></pre>\n";
- }
-
- public static function newSection($sHeading)
- {
- echo "<hr><pre><h3>$sHeading</h3></pre>\n";
- }
-
- public static function printVar($sHeading, $mVar)
- {
- echo '<pre><b>'.$sHeading. ':</b> ';
- Debug::outputVar($mVar, str_repeat(' ', strlen($sHeading) + 3));
- echo "</pre>\n";
- }
-
- public static function fmtArrayVals($aArr)
- {
- return array('__debug_format' => 'array_vals', 'data' => $aArr);
- }
-
- public static function printDebugArray($sHeading, $oVar)
- {
-
- if ($oVar === null) {
- Debug::printVar($sHeading, 'null');
- } else {
- Debug::printVar($sHeading, $oVar->debugInfo());
- }
- }
-
- public static function printDebugTable($sHeading, $aVar)
- {
- echo '<b>'.$sHeading.":</b>\n";
- echo "<table border='1'>\n";
- if (!empty($aVar)) {
- echo " <tr>\n";
- $aKeys = array();
- $aInfo = reset($aVar);
- if (!is_array($aInfo)) {
- $aInfo = $aInfo->debugInfo();
- }
- foreach ($aInfo as $sKey => $mVal) {
- echo ' <th><small>'.$sKey.'</small></th>'."\n";
- $aKeys[] = $sKey;
- }
- echo " </tr>\n";
- foreach ($aVar as $oRow) {
- $aInfo = $oRow;
- if (!is_array($oRow)) {
- $aInfo = $oRow->debugInfo();
- }
- echo " <tr>\n";
- foreach ($aKeys as $sKey) {
- echo ' <td><pre>';
- if (isset($aInfo[$sKey])) {
- Debug::outputVar($aInfo[$sKey], '');
- }
- echo '</pre></td>'."\n";
- }
- echo " </tr>\n";
- }
- }
- echo "</table>\n";
- }
-
- public static function printGroupedSearch($aSearches, $aWordsIDs)
- {
- echo '<table border="1">';
- echo '<tr><th>rank</th><th>Name Tokens</th><th>Name Not</th>';
- echo '<th>Address Tokens</th><th>Address Not</th>';
- echo '<th>country</th><th>operator</th>';
- echo '<th>class</th><th>type</th><th>postcode</th><th>housenumber</th></tr>';
- foreach ($aSearches as $aRankedSet) {
- foreach ($aRankedSet as $aRow) {
- $aRow->dumpAsHtmlTableRow($aWordsIDs);
- }
- }
- echo '</table>';
- }
-
- public static function printGroupTable($sHeading, $aVar)
- {
- echo '<b>'.$sHeading.":</b>\n";
- echo "<table border='1'>\n";
- if (!empty($aVar)) {
- echo " <tr>\n";
- echo ' <th><small>Group</small></th>'."\n";
- $aKeys = array();
- $aInfo = reset($aVar)[0];
- if (!is_array($aInfo)) {
- $aInfo = $aInfo->debugInfo();
- }
- foreach ($aInfo as $sKey => $mVal) {
- echo ' <th><small>'.$sKey.'</small></th>'."\n";
- $aKeys[] = $sKey;
- }
- echo " </tr>\n";
- foreach ($aVar as $sGrpKey => $aGroup) {
- foreach ($aGroup as $oRow) {
- $aInfo = $oRow;
- if (!is_array($oRow)) {
- $aInfo = $oRow->debugInfo();
- }
- echo " <tr>\n";
- echo ' <td><pre>'.$sGrpKey.'</pre></td>'."\n";
- foreach ($aKeys as $sKey) {
- echo ' <td><pre>';
- if (!empty($aInfo[$sKey])) {
- Debug::outputVar($aInfo[$sKey], '');
- }
- echo '</pre></td>'."\n";
- }
- echo " </tr>\n";
- }
- }
- }
- echo "</table>\n";
- }
-
- public static function printSQL($sSQL)
- {
- echo '<p><tt><b>'.date('c').'</b> <font color="#aaa">'.htmlspecialchars($sSQL, ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401).'</font></tt></p>'."\n";
- }
-
- private static function outputVar($mVar, $sPreNL)
- {
- if (is_array($mVar) && !isset($mVar['__debug_format'])) {
- $sPre = '';
- foreach ($mVar as $mKey => $aValue) {
- echo $sPre;
- $iKeyLen = Debug::outputSimpleVar($mKey);
- echo ' => ';
- Debug::outputVar(
- $aValue,
- $sPreNL.str_repeat(' ', $iKeyLen + 4)
- );
- $sPre = "\n".$sPreNL;
- }
- } elseif (is_array($mVar) && isset($mVar['__debug_format'])) {
- if (!empty($mVar['data'])) {
- $sPre = '';
- foreach ($mVar['data'] as $mValue) {
- echo $sPre;
- Debug::outputSimpleVar($mValue);
- $sPre = ', ';
- }
- }
- } elseif (is_object($mVar) && method_exists($mVar, 'debugInfo')) {
- Debug::outputVar($mVar->debugInfo(), $sPreNL);
- } elseif (is_a($mVar, 'stdClass')) {
- Debug::outputVar(json_decode(json_encode($mVar), true), $sPreNL);
- } else {
- Debug::outputSimpleVar($mVar);
- }
- }
-
- private static function outputSimpleVar($mVar)
- {
- if (is_bool($mVar)) {
- echo '<i>'.($mVar ? 'True' : 'False').'</i>';
- return $mVar ? 4 : 5;
- }
-
- if (is_string($mVar)) {
- $sOut = "'$mVar'";
- } else {
- $sOut = (string)$mVar;
- }
-
- echo htmlspecialchars($sOut, ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401);
- return strlen($sOut);
- }
-}
+++ /dev/null
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-namespace Nominatim;
-
-class Debug
-{
- public static function __callStatic($name, $arguments)
- {
- // nothing
- }
-}
+++ /dev/null
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-namespace Nominatim;
-
-require_once(CONST_LibDir.'/PlaceLookup.php');
-require_once(CONST_LibDir.'/Phrase.php');
-require_once(CONST_LibDir.'/ReverseGeocode.php');
-require_once(CONST_LibDir.'/SearchDescription.php');
-require_once(CONST_LibDir.'/SearchContext.php');
-require_once(CONST_LibDir.'/SearchPosition.php');
-require_once(CONST_LibDir.'/TokenList.php');
-require_once(CONST_TokenizerDir.'/tokenizer.php');
-
-class Geocode
-{
- protected $oDB;
-
- protected $oPlaceLookup;
- protected $oTokenizer;
-
- protected $aLangPrefOrder = array();
-
- protected $aExcludePlaceIDs = array();
-
- protected $iLimit = 20;
- protected $iFinalLimit = 10;
- protected $iOffset = 0;
- protected $bFallback = false;
-
- protected $aCountryCodes = false;
-
- protected $bBoundedSearch = false;
- protected $aViewBox = false;
- protected $aRoutePoints = false;
- protected $aRouteWidth = false;
-
- protected $iMaxRank = 20;
- protected $iMinAddressRank = 0;
- protected $iMaxAddressRank = 30;
- protected $aAddressRankList = array();
-
- protected $sAllowedTypesSQLList = false;
-
- protected $sQuery = false;
- protected $aStructuredQuery = false;
-
-
- public function __construct(&$oDB)
- {
- $this->oDB =& $oDB;
- $this->oPlaceLookup = new PlaceLookup($this->oDB);
- $this->oTokenizer = new \Nominatim\Tokenizer($this->oDB);
- }
-
- public function setLanguagePreference($aLangPref)
- {
- $this->aLangPrefOrder = $aLangPref;
- }
-
- public function getMoreUrlParams()
- {
- if ($this->aStructuredQuery) {
- $aParams = $this->aStructuredQuery;
- } else {
- $aParams = array('q' => $this->sQuery);
- }
-
- $aParams = array_merge($aParams, $this->oPlaceLookup->getMoreUrlParams());
-
- if ($this->aExcludePlaceIDs) {
- $aParams['exclude_place_ids'] = implode(',', $this->aExcludePlaceIDs);
- }
-
- if ($this->bBoundedSearch) {
- $aParams['bounded'] = '1';
- }
-
- if ($this->aCountryCodes) {
- $aParams['countrycodes'] = implode(',', $this->aCountryCodes);
- }
-
- if ($this->aViewBox) {
- $aParams['viewbox'] = join(',', $this->aViewBox);
- }
-
- return $aParams;
- }
-
- public function setLimit($iLimit = 10)
- {
- if ($iLimit > 50) {
- $iLimit = 50;
- } elseif ($iLimit < 1) {
- $iLimit = 1;
- }
-
- $this->iFinalLimit = $iLimit;
- $this->iLimit = $iLimit + max($iLimit, 10);
- }
-
- public function setFeatureType($sFeatureType)
- {
- switch ($sFeatureType) {
- case 'country':
- $this->setRankRange(4, 4);
- break;
- case 'state':
- $this->setRankRange(8, 8);
- break;
- case 'city':
- $this->setRankRange(14, 16);
- break;
- case 'settlement':
- $this->setRankRange(8, 20);
- break;
- }
- }
-
- public function setRankRange($iMin, $iMax)
- {
- $this->iMinAddressRank = $iMin;
- $this->iMaxAddressRank = $iMax;
- }
-
- public function setViewbox($aViewbox)
- {
- $aBox = array_map('floatval', $aViewbox);
-
- $this->aViewBox[0] = max(-180.0, min($aBox[0], $aBox[2]));
- $this->aViewBox[1] = max(-90.0, min($aBox[1], $aBox[3]));
- $this->aViewBox[2] = min(180.0, max($aBox[0], $aBox[2]));
- $this->aViewBox[3] = min(90.0, max($aBox[1], $aBox[3]));
-
- if ($this->aViewBox[2] - $this->aViewBox[0] < 0.000000001
- || $this->aViewBox[3] - $this->aViewBox[1] < 0.000000001
- ) {
- userError("Bad parameter 'viewbox'. Not a box.");
- }
- }
-
- private function viewboxImportanceFactor($fX, $fY)
- {
- if (!$this->aViewBox) {
- return 1;
- }
-
- $fWidth = ($this->aViewBox[2] - $this->aViewBox[0])/2;
- $fHeight = ($this->aViewBox[3] - $this->aViewBox[1])/2;
-
- $fXDist = abs($fX - ($this->aViewBox[0] + $this->aViewBox[2])/2);
- $fYDist = abs($fY - ($this->aViewBox[1] + $this->aViewBox[3])/2);
-
- if ($fXDist <= $fWidth && $fYDist <= $fHeight) {
- return 1;
- }
-
- if ($fXDist <= $fWidth * 3 && $fYDist <= 3 * $fHeight) {
- return 0.5;
- }
-
- return 0.25;
- }
-
- public function setQuery($sQueryString)
- {
- $this->sQuery = $sQueryString;
- $this->aStructuredQuery = false;
- }
-
- public function getQueryString()
- {
- return $this->sQuery;
- }
-
-
- public function loadParamArray($oParams, $sForceGeometryType = null)
- {
- $this->bBoundedSearch = $oParams->getBool('bounded', $this->bBoundedSearch);
-
- $this->setLimit($oParams->getInt('limit', $this->iFinalLimit));
- $this->iOffset = $oParams->getInt('offset', $this->iOffset);
-
- $this->bFallback = $oParams->getBool('fallback', $this->bFallback);
-
- // List of excluded Place IDs - used for more accurate pageing
- $sExcluded = $oParams->getStringList('exclude_place_ids');
- if ($sExcluded) {
- foreach ($sExcluded as $iExcludedPlaceID) {
- $iExcludedPlaceID = (int)$iExcludedPlaceID;
- if ($iExcludedPlaceID) {
- $aExcludePlaceIDs[$iExcludedPlaceID] = $iExcludedPlaceID;
- }
- }
-
- if (isset($aExcludePlaceIDs)) {
- $this->aExcludePlaceIDs = $aExcludePlaceIDs;
- }
- }
-
- // Only certain ranks of feature
- $sFeatureType = $oParams->getString('featureType');
- if (!$sFeatureType) {
- $sFeatureType = $oParams->getString('featuretype');
- }
- if ($sFeatureType) {
- $this->setFeatureType($sFeatureType);
- }
-
- // Country code list
- $sCountries = $oParams->getStringList('countrycodes');
- if ($sCountries) {
- foreach ($sCountries as $sCountryCode) {
- if (preg_match('/^[a-zA-Z][a-zA-Z]$/', $sCountryCode)) {
- $aCountries[] = strtolower($sCountryCode);
- }
- }
- if (isset($aCountries)) {
- $this->aCountryCodes = $aCountries;
- }
- }
-
- $aViewbox = $oParams->getStringList('viewboxlbrt');
- if ($aViewbox) {
- if (count($aViewbox) != 4) {
- userError("Bad parameter 'viewboxlbrt'. Expected 4 coordinates.");
- }
- $this->setViewbox($aViewbox);
- } else {
- $aViewbox = $oParams->getStringList('viewbox');
- if ($aViewbox) {
- if (count($aViewbox) != 4) {
- userError("Bad parameter 'viewbox'. Expected 4 coordinates.");
- }
- $this->setViewBox($aViewbox);
- } else {
- $aRoute = $oParams->getStringList('route');
- $fRouteWidth = $oParams->getFloat('routewidth');
- if ($aRoute && $fRouteWidth) {
- $this->aRoutePoints = $aRoute;
- $this->aRouteWidth = $fRouteWidth;
- }
- }
- }
-
- $this->oPlaceLookup->loadParamArray($oParams, $sForceGeometryType);
- $this->oPlaceLookup->setIncludeAddressDetails($oParams->getBool('addressdetails', false));
- }
-
- public function setQueryFromParams($oParams)
- {
- // Search query
- $sQuery = $oParams->getString('q');
- if (!$sQuery) {
- $this->setStructuredQuery(
- $oParams->getString('amenity'),
- $oParams->getString('street'),
- $oParams->getString('city'),
- $oParams->getString('county'),
- $oParams->getString('state'),
- $oParams->getString('country'),
- $oParams->getString('postalcode')
- );
- } else {
- $this->setQuery($sQuery);
- }
- }
-
- public function loadStructuredAddressElement($sValue, $sKey, $iNewMinAddressRank, $iNewMaxAddressRank, $aItemListValues)
- {
- $sValue = trim($sValue);
- if (!$sValue) {
- return false;
- }
- $this->aStructuredQuery[$sKey] = $sValue;
- if ($this->iMinAddressRank == 0 && $this->iMaxAddressRank == 30) {
- $this->iMinAddressRank = $iNewMinAddressRank;
- $this->iMaxAddressRank = $iNewMaxAddressRank;
- }
- if ($aItemListValues) {
- $this->aAddressRankList = array_merge($this->aAddressRankList, $aItemListValues);
- }
- return true;
- }
-
- public function setStructuredQuery($sAmenity = false, $sStreet = false, $sCity = false, $sCounty = false, $sState = false, $sCountry = false, $sPostalCode = false)
- {
- $this->sQuery = false;
-
- // Reset
- $this->iMinAddressRank = 0;
- $this->iMaxAddressRank = 30;
- $this->aAddressRankList = array();
-
- $this->aStructuredQuery = array();
- $this->sAllowedTypesSQLList = false;
-
- $this->loadStructuredAddressElement($sAmenity, 'amenity', 26, 30, false);
- $this->loadStructuredAddressElement($sStreet, 'street', 26, 30, false);
- $this->loadStructuredAddressElement($sCity, 'city', 14, 24, false);
- $this->loadStructuredAddressElement($sCounty, 'county', 9, 13, false);
- $this->loadStructuredAddressElement($sState, 'state', 8, 8, false);
- $this->loadStructuredAddressElement($sPostalCode, 'postalcode', 5, 11, array(5, 11));
- $this->loadStructuredAddressElement($sCountry, 'country', 4, 4, false);
-
- if (!empty($this->aStructuredQuery)) {
- $this->sQuery = join(', ', $this->aStructuredQuery);
- if ($this->iMaxAddressRank < 30) {
- $this->sAllowedTypesSQLList = '(\'place\',\'boundary\')';
- }
- }
- }
-
- public function fallbackStructuredQuery()
- {
- $aParams = $this->aStructuredQuery;
-
- if (!$aParams || count($aParams) == 1) {
- return false;
- }
-
- $aOrderToFallback = array('postalcode', 'street', 'city', 'county', 'state');
-
- foreach ($aOrderToFallback as $sType) {
- if (isset($aParams[$sType])) {
- unset($aParams[$sType]);
- $this->setStructuredQuery(@$aParams['amenity'], @$aParams['street'], @$aParams['city'], @$aParams['county'], @$aParams['state'], @$aParams['country'], @$aParams['postalcode']);
- return true;
- }
- }
-
- return false;
- }
-
- public function getGroupedSearches($aSearches, $aPhrases, $oValidTokens)
- {
- /*
- Calculate all searches using oValidTokens i.e.
- 'Wodsworth Road, Sheffield' =>
-
- Phrase Wordset
- 0 0 (wodsworth road)
- 0 1 (wodsworth)(road)
- 1 0 (sheffield)
-
- Score how good the search is so they can be ordered
- */
- foreach ($aPhrases as $iPhrase => $oPhrase) {
- $aNewPhraseSearches = array();
- $oPosition = new SearchPosition(
- $oPhrase->getPhraseType(),
- $iPhrase,
- count($aPhrases)
- );
-
- foreach ($oPhrase->getWordSets() as $aWordset) {
- $aWordsetSearches = $aSearches;
-
- // Add all words from this wordset
- foreach ($aWordset as $iToken => $sToken) {
- $aNewWordsetSearches = array();
- $oPosition->setTokenPosition($iToken, count($aWordset));
-
- foreach ($aWordsetSearches as $oCurrentSearch) {
- foreach ($oValidTokens->get($sToken) as $oSearchTerm) {
- if ($oSearchTerm->isExtendable($oCurrentSearch, $oPosition)) {
- $aNewSearches = $oSearchTerm->extendSearch(
- $oCurrentSearch,
- $oPosition
- );
-
- foreach ($aNewSearches as $oSearch) {
- if ($oSearch->getRank() < $this->iMaxRank) {
- $aNewWordsetSearches[] = $oSearch;
- }
- }
- }
- }
- }
- // Sort and cut
- usort($aNewWordsetSearches, array('Nominatim\SearchDescription', 'bySearchRank'));
- $aWordsetSearches = array_slice($aNewWordsetSearches, 0, 50);
- }
-
- $aNewPhraseSearches = array_merge($aNewPhraseSearches, $aNewWordsetSearches);
- usort($aNewPhraseSearches, array('Nominatim\SearchDescription', 'bySearchRank'));
-
- $aSearchHash = array();
- foreach ($aNewPhraseSearches as $iSearch => $aSearch) {
- $sHash = serialize($aSearch);
- if (isset($aSearchHash[$sHash])) {
- unset($aNewPhraseSearches[$iSearch]);
- } else {
- $aSearchHash[$sHash] = 1;
- }
- }
-
- $aNewPhraseSearches = array_slice($aNewPhraseSearches, 0, 50);
- }
-
- // Re-group the searches by their score, junk anything over 20 as just not worth trying
- $aGroupedSearches = array();
- foreach ($aNewPhraseSearches as $aSearch) {
- $iRank = $aSearch->getRank();
- if ($iRank < $this->iMaxRank) {
- if (!isset($aGroupedSearches[$iRank])) {
- $aGroupedSearches[$iRank] = array();
- }
- $aGroupedSearches[$iRank][] = $aSearch;
- }
- }
- ksort($aGroupedSearches);
-
- $iSearchCount = 0;
- $aSearches = array();
- foreach ($aGroupedSearches as $aNewSearches) {
- $iSearchCount += count($aNewSearches);
- $aSearches = array_merge($aSearches, $aNewSearches);
- if ($iSearchCount > 50) {
- break;
- }
- }
- }
-
- // Revisit searches, drop bad searches and give penalty to unlikely combinations.
- $aGroupedSearches = array();
- foreach ($aSearches as $oSearch) {
- if (!$oSearch->isValidSearch()) {
- continue;
- }
-
- $iRank = $oSearch->getRank();
- if (!isset($aGroupedSearches[$iRank])) {
- $aGroupedSearches[$iRank] = array();
- }
- $aGroupedSearches[$iRank][] = $oSearch;
- }
- ksort($aGroupedSearches);
-
- return $aGroupedSearches;
- }
-
- /* Perform the actual query lookup.
-
- Returns an ordered list of results, each with the following fields:
- osm_type: type of corresponding OSM object
- N - node
- W - way
- R - relation
- P - postcode (internally computed)
- osm_id: id of corresponding OSM object
- class: general object class (corresponds to tag key of primary OSM tag)
- type: subclass of object (corresponds to tag value of primary OSM tag)
- admin_level: see https://wiki.openstreetmap.org/wiki/Admin_level
- rank_search: rank in search hierarchy
- (see also https://wiki.openstreetmap.org/wiki/Nominatim/Development_overview#Country_to_street_level)
- rank_address: rank in address hierarchy (determines orer in address)
- place_id: internal key (may differ between different instances)
- country_code: ISO country code
- langaddress: localized full address
- placename: localized name of object
- ref: content of ref tag (if available)
- lon: longitude
- lat: latitude
- importance: importance of place based on Wikipedia link count
- addressimportance: cumulated importance of address elements
- extra_place: type of place (for admin boundaries, if there is a place tag)
- aBoundingBox: bounding Box
- label: short description of the object class/type (English only)
- name: full name (currently the same as langaddress)
- foundorder: secondary ordering for places with same importance
- */
-
-
- public function lookup()
- {
- Debug::newFunction('Geocode::lookup');
- if (!$this->sQuery && !$this->aStructuredQuery) {
- return array();
- }
-
- Debug::printDebugArray('Geocode', $this);
-
- $oCtx = new SearchContext();
-
- if ($this->aRoutePoints) {
- $oCtx->setViewboxFromRoute(
- $this->oDB,
- $this->aRoutePoints,
- $this->aRouteWidth,
- $this->bBoundedSearch
- );
- } elseif ($this->aViewBox) {
- $oCtx->setViewboxFromBox($this->aViewBox, $this->bBoundedSearch);
- }
- if ($this->aExcludePlaceIDs) {
- $oCtx->setExcludeList($this->aExcludePlaceIDs);
- }
- if ($this->aCountryCodes) {
- $oCtx->setCountryList($this->aCountryCodes);
- }
-
- Debug::newSection('Query Preprocessing');
-
- $sQuery = $this->sQuery;
- if (!preg_match('//u', $sQuery)) {
- userError('Query string is not UTF-8 encoded.');
- }
-
- // Do we have anything that looks like a lat/lon pair?
- $sQuery = $oCtx->setNearPointFromQuery($sQuery);
-
- if ($sQuery || $this->aStructuredQuery) {
- // Start with a single blank search
- $aSearches = array(new SearchDescription($oCtx));
-
- if ($sQuery) {
- $sQuery = $aSearches[0]->extractKeyValuePairs($sQuery);
- }
-
- $sSpecialTerm = '';
- if ($sQuery) {
- preg_match_all(
- '/\\[([\\w ]*)\\]/u',
- $sQuery,
- $aSpecialTermsRaw,
- PREG_SET_ORDER
- );
- if (!empty($aSpecialTermsRaw)) {
- Debug::printVar('Special terms', $aSpecialTermsRaw);
- }
-
- foreach ($aSpecialTermsRaw as $aSpecialTerm) {
- $sQuery = str_replace($aSpecialTerm[0], ' ', $sQuery);
- if (!$sSpecialTerm) {
- $sSpecialTerm = $aSpecialTerm[1];
- }
- }
- }
- if (!$sSpecialTerm && $this->aStructuredQuery
- && isset($this->aStructuredQuery['amenity'])) {
- $sSpecialTerm = $this->aStructuredQuery['amenity'];
- unset($this->aStructuredQuery['amenity']);
- }
-
- if ($sSpecialTerm && !$aSearches[0]->hasOperator()) {
- $aTokens = $this->oTokenizer->tokensForSpecialTerm($sSpecialTerm);
-
- if (!empty($aTokens)) {
- $aNewSearches = array();
- $oPosition = new SearchPosition('', 0, 1);
- $oPosition->setTokenPosition(0, 1);
-
- foreach ($aSearches as $oSearch) {
- foreach ($aTokens as $oToken) {
- $aNewSearches = array_merge(
- $aNewSearches,
- $oToken->extendSearch($oSearch, $oPosition)
- );
- }
- }
- $aSearches = $aNewSearches;
- }
- }
-
- // Split query into phrases
- // Commas are used to reduce the search space by indicating where phrases split
- $aPhrases = array();
- if ($this->aStructuredQuery) {
- foreach ($this->aStructuredQuery as $iPhrase => $sPhrase) {
- $aPhrases[] = new Phrase($sPhrase, $iPhrase);
- }
- } else {
- foreach (explode(',', $sQuery) as $sPhrase) {
- $aPhrases[] = new Phrase($sPhrase, '');
- }
- }
-
- Debug::printDebugArray('Search context', $oCtx);
- Debug::printDebugArray('Base search', empty($aSearches) ? null : $aSearches[0]);
-
- Debug::newSection('Tokenization');
- $oValidTokens = $this->oTokenizer->extractTokensFromPhrases($aPhrases);
-
- if ($oValidTokens->count() > 0) {
- $oCtx->setFullNameWords($oValidTokens->getFullWordIDs());
-
- $aPhrases = array_filter($aPhrases, function ($oPhrase) {
- return $oPhrase->getWordSets() !== null;
- });
-
- // Any words that have failed completely?
- // TODO: suggestions
-
- Debug::printGroupTable('Valid Tokens', $oValidTokens->debugInfo());
- Debug::printDebugTable('Phrases', $aPhrases);
-
- Debug::newSection('Search candidates');
-
- $aGroupedSearches = $this->getGroupedSearches($aSearches, $aPhrases, $oValidTokens);
-
- if (!$this->aStructuredQuery) {
- // Reverse phrase array and also reverse the order of the wordsets in
- // the first and final phrase. Don't bother about phrases in the middle
- // because order in the address doesn't matter.
- $aPhrases = array_reverse($aPhrases);
- $aPhrases[0]->invertWordSets();
- if (count($aPhrases) > 1) {
- $aPhrases[count($aPhrases)-1]->invertWordSets();
- }
- $aReverseGroupedSearches = $this->getGroupedSearches($aSearches, $aPhrases, $oValidTokens);
-
- foreach ($aReverseGroupedSearches as $aSearches) {
- foreach ($aSearches as $aSearch) {
- if (!isset($aGroupedSearches[$aSearch->getRank()])) {
- $aGroupedSearches[$aSearch->getRank()] = array();
- }
- $aGroupedSearches[$aSearch->getRank()][] = $aSearch;
- }
- }
-
- ksort($aGroupedSearches);
- }
- } else {
- // Re-group the searches by their score, junk anything over 20 as just not worth trying
- $aGroupedSearches = array();
- foreach ($aSearches as $aSearch) {
- if ($aSearch->getRank() < $this->iMaxRank) {
- if (!isset($aGroupedSearches[$aSearch->getRank()])) {
- $aGroupedSearches[$aSearch->getRank()] = array();
- }
- $aGroupedSearches[$aSearch->getRank()][] = $aSearch;
- }
- }
- ksort($aGroupedSearches);
- }
-
- // Filter out duplicate searches
- $aSearchHash = array();
- foreach ($aGroupedSearches as $iGroup => $aSearches) {
- foreach ($aSearches as $iSearch => $aSearch) {
- $sHash = serialize($aSearch);
- if (isset($aSearchHash[$sHash])) {
- unset($aGroupedSearches[$iGroup][$iSearch]);
- if (empty($aGroupedSearches[$iGroup])) {
- unset($aGroupedSearches[$iGroup]);
- }
- } else {
- $aSearchHash[$sHash] = 1;
- }
- }
- }
-
- Debug::printGroupedSearch(
- $aGroupedSearches,
- $oValidTokens->debugTokenByWordIdList()
- );
-
- // Start the search process
- $iGroupLoop = 0;
- $iQueryLoop = 0;
- $aNextResults = array();
- foreach ($aGroupedSearches as $iGroupedRank => $aSearches) {
- $iGroupLoop++;
- $aResults = $aNextResults;
- foreach ($aSearches as $oSearch) {
- $iQueryLoop++;
-
- Debug::newSection("Search Loop, group $iGroupLoop, loop $iQueryLoop");
- Debug::printGroupedSearch(
- array($iGroupedRank => array($oSearch)),
- $oValidTokens->debugTokenByWordIdList()
- );
-
- $aNewResults = $oSearch->query(
- $this->oDB,
- $this->iMinAddressRank,
- $this->iMaxAddressRank,
- $this->iLimit
- );
-
- // The same result may appear in different rounds, only
- // use the one with minimal rank.
- foreach ($aNewResults as $iPlace => $oRes) {
- if (!isset($aResults[$iPlace])
- || $aResults[$iPlace]->iResultRank > $oRes->iResultRank) {
- $aResults[$iPlace] = $oRes;
- }
- }
-
- if ($iQueryLoop > 20) {
- break;
- }
- }
-
- if (!empty($aResults)) {
- $aSplitResults = Result::splitResults($aResults);
- Debug::printVar('Split results', $aSplitResults);
- if ($iGroupLoop <= 4
- && reset($aSplitResults['head'])->iResultRank > 0
- && $iGroupedRank !== array_key_last($aGroupedSearches)) {
- // Haven't found an exact match for the query yet.
- // Therefore add result from the next group level.
- $aNextResults = $aSplitResults['head'];
- foreach ($aNextResults as $oRes) {
- $oRes->iResultRank--;
- }
- foreach ($aSplitResults['tail'] as $oRes) {
- $oRes->iResultRank--;
- $aNextResults[$oRes->iId] = $oRes;
- }
- $aResults = array();
- } else {
- $aResults = $aSplitResults['head'];
- }
- }
-
- if (!empty($aResults) && ($this->iMinAddressRank != 0 || $this->iMaxAddressRank != 30)) {
- // Need to verify passes rank limits before dropping out of the loop (yuk!)
- // reduces the number of place ids, like a filter
- // rank_address is 30 for interpolated housenumbers
- $aFilterSql = array();
- $sPlaceIds = Result::joinIdsByTable($aResults, Result::TABLE_PLACEX);
- if ($sPlaceIds) {
- $sSQL = 'SELECT place_id FROM placex ';
- $sSQL .= 'WHERE place_id in ('.$sPlaceIds.') ';
- $sSQL .= ' AND (';
- $sSQL .= " placex.rank_address between $this->iMinAddressRank and $this->iMaxAddressRank ";
- $sSQL .= " OR placex.rank_search between $this->iMinAddressRank and $this->iMaxAddressRank ";
- if ($this->aAddressRankList) {
- $sSQL .= ' OR placex.rank_address in ('.join(',', $this->aAddressRankList).')';
- }
- $sSQL .= ')';
- $aFilterSql[] = $sSQL;
- }
- $sPlaceIds = Result::joinIdsByTable($aResults, Result::TABLE_POSTCODE);
- if ($sPlaceIds) {
- $sSQL = ' SELECT place_id FROM location_postcode lp ';
- $sSQL .= 'WHERE place_id in ('.$sPlaceIds.') ';
- $sSQL .= " AND (lp.rank_address between $this->iMinAddressRank and $this->iMaxAddressRank ";
- if ($this->aAddressRankList) {
- $sSQL .= ' OR lp.rank_address in ('.join(',', $this->aAddressRankList).')';
- }
- $sSQL .= ') ';
- $aFilterSql[] = $sSQL;
- }
-
- $aFilteredIDs = array();
- if ($aFilterSql) {
- $sSQL = join(' UNION ', $aFilterSql);
- Debug::printSQL($sSQL);
- $aFilteredIDs = $this->oDB->getCol($sSQL);
- }
-
- $tempIDs = array();
- foreach ($aResults as $oResult) {
- if (($this->iMaxAddressRank == 30 &&
- ($oResult->iTable == Result::TABLE_OSMLINE
- || $oResult->iTable == Result::TABLE_TIGER))
- || in_array($oResult->iId, $aFilteredIDs)
- ) {
- $tempIDs[$oResult->iId] = $oResult;
- }
- }
- $aResults = $tempIDs;
- }
-
- if (!empty($aResults) || $iGroupLoop > 4 || $iQueryLoop > 30) {
- break;
- }
- }
- } else {
- // Just interpret as a reverse geocode
- $oReverse = new ReverseGeocode($this->oDB);
- $oReverse->setZoom(18);
-
- $oLookup = $oReverse->lookupPoint($oCtx->sqlNear, false);
-
- Debug::printVar('Reverse search', $oLookup);
-
- if ($oLookup) {
- $aResults = array($oLookup->iId => $oLookup);
- }
- }
-
- // No results? Done
- if (empty($aResults)) {
- if ($this->bFallback && $this->fallbackStructuredQuery()) {
- return $this->lookup();
- }
-
- return array();
- }
-
- if ($this->aAddressRankList) {
- $this->oPlaceLookup->setAddressRankList($this->aAddressRankList);
- }
- $this->oPlaceLookup->setAllowedTypesSQLList($this->sAllowedTypesSQLList);
- $this->oPlaceLookup->setLanguagePreference($this->aLangPrefOrder);
- if ($oCtx->hasNearPoint()) {
- $this->oPlaceLookup->setAnchorSql($oCtx->sqlNear);
- }
-
- $aSearchResults = $this->oPlaceLookup->lookup($aResults);
-
- $aRecheckWords = preg_split('/\b[\s,\\-]*/u', $sQuery);
- foreach ($aRecheckWords as $i => $sWord) {
- if (!preg_match('/[\pL\pN]/', $sWord)) {
- unset($aRecheckWords[$i]);
- }
- }
-
- Debug::printVar('Recheck words', $aRecheckWords);
-
- foreach ($aSearchResults as $iIdx => $aResult) {
- $fRadius = ClassTypes\getDefRadius($aResult);
-
- $aOutlineResult = $this->oPlaceLookup->getOutlines($aResult['place_id'], $aResult['lon'], $aResult['lat'], $fRadius);
- if ($aOutlineResult) {
- $aResult = array_merge($aResult, $aOutlineResult);
- }
-
- // Is there an icon set for this type of result?
- $sIcon = ClassTypes\getIconFile($aResult);
- if (isset($sIcon)) {
- $aResult['icon'] = $sIcon;
- }
-
- $sLabel = ClassTypes\getLabel($aResult);
- if (isset($sLabel)) {
- $aResult['label'] = $sLabel;
- }
- $aResult['name'] = $aResult['langaddress'];
-
- if ($oCtx->hasNearPoint()) {
- $aResult['importance'] = 0.001;
- $aResult['foundorder'] = $aResult['addressimportance'];
- } else {
- if ($aResult['importance'] == 0) {
- $aResult['importance'] = 0.0001;
- }
- $aResult['importance'] *= $this->viewboxImportanceFactor(
- $aResult['lon'],
- $aResult['lat']
- );
-
- // secondary ordering (for results with same importance (the smaller the better):
- // - approximate importance of address parts
- if (isset($aResult['addressimportance']) && $aResult['addressimportance']) {
- $aResult['foundorder'] = -$aResult['addressimportance']/10;
- } else {
- $aResult['foundorder'] = -$aResult['importance'];
- }
- // - number of exact matches from the query
- $aResult['foundorder'] -= $aResults[$aResult['place_id']]->iExactMatches;
- // - importance of the class/type
- $iClassImportance = ClassTypes\getImportance($aResult);
- if (isset($iClassImportance)) {
- $aResult['foundorder'] += 0.0001 * $iClassImportance;
- } else {
- $aResult['foundorder'] += 0.01;
- }
- // - rank
- $aResult['foundorder'] -= 0.00001 * (30 - $aResult['rank_search']);
-
- // Adjust importance for the number of exact string matches in the result
- $iCountWords = 0;
- $sAddress = $aResult['langaddress'];
- foreach ($aRecheckWords as $i => $sWord) {
- if (grapheme_stripos($sAddress, $sWord)!==false) {
- $iCountWords++;
- if (preg_match('/(^|,)\s*'.preg_quote($sWord, '/').'\s*(,|$)/', $sAddress)) {
- $iCountWords += 0.1;
- }
- }
- }
-
- // 0.1 is a completely arbitrary number but something in the range 0.1 to 0.5 would seem right
- $aResult['importance'] = $aResult['importance'] + ($iCountWords*0.1);
- }
- $aSearchResults[$iIdx] = $aResult;
- }
- uasort($aSearchResults, 'byImportance');
- Debug::printVar('Pre-filter results', $aSearchResults);
-
- $aOSMIDDone = array();
- $aClassTypeNameDone = array();
- $aToFilter = $aSearchResults;
- $aSearchResults = array();
-
- foreach ($aToFilter as $aResult) {
- $this->aExcludePlaceIDs[$aResult['place_id']] = $aResult['place_id'];
- if (!$this->oPlaceLookup->doDeDupe() || (!isset($aOSMIDDone[$aResult['osm_type'].$aResult['osm_id']])
- && !isset($aClassTypeNameDone[$aResult['osm_type'].$aResult['class'].$aResult['type'].$aResult['name'].$aResult['admin_level']]))
- ) {
- $aOSMIDDone[$aResult['osm_type'].$aResult['osm_id']] = true;
- $aClassTypeNameDone[$aResult['osm_type'].$aResult['class'].$aResult['type'].$aResult['name'].$aResult['admin_level']] = true;
- $aSearchResults[] = $aResult;
- }
-
- // Absolute limit on number of results
- if (count($aSearchResults) >= $this->iFinalLimit) {
- break;
- }
- }
-
- Debug::printVar('Post-filter results', $aSearchResults);
- return $aSearchResults;
- } // end lookup()
-
- public function debugInfo()
- {
- return array(
- 'Query' => $this->sQuery,
- 'Structured query' => $this->aStructuredQuery,
- 'Name keys' => Debug::fmtArrayVals($this->aLangPrefOrder),
- 'Excluded place IDs' => Debug::fmtArrayVals($this->aExcludePlaceIDs),
- 'Limit (for searches)' => $this->iLimit,
- 'Limit (for results)'=> $this->iFinalLimit,
- 'Country codes' => Debug::fmtArrayVals($this->aCountryCodes),
- 'Bounded search' => $this->bBoundedSearch,
- 'Viewbox' => Debug::fmtArrayVals($this->aViewBox),
- 'Route points' => Debug::fmtArrayVals($this->aRoutePoints),
- 'Route width' => $this->aRouteWidth,
- 'Max rank' => $this->iMaxRank,
- 'Min address rank' => $this->iMinAddressRank,
- 'Max address rank' => $this->iMaxAddressRank,
- 'Address rank list' => Debug::fmtArrayVals($this->aAddressRankList)
- );
- }
-} // end class
+++ /dev/null
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-namespace Nominatim;
-
-class ParameterParser
-{
- private $aParams;
-
-
- public function __construct($aParams = null)
- {
- $this->aParams = ($aParams === null) ? $_GET : $aParams;
- }
-
- public function getBool($sName, $bDefault = false)
- {
- if (!isset($this->aParams[$sName])
- || !is_string($this->aParams[$sName])
- || strlen($this->aParams[$sName]) == 0
- ) {
- return $bDefault;
- }
-
- return (bool) $this->aParams[$sName];
- }
-
- public function getInt($sName, $bDefault = false)
- {
- if (!isset($this->aParams[$sName]) || is_array($this->aParams[$sName])) {
- return $bDefault;
- }
-
- if (!preg_match('/^[+-]?[0-9]+$/', $this->aParams[$sName])) {
- userError("Integer number expected for parameter '$sName'");
- }
-
- return (int) $this->aParams[$sName];
- }
-
- public function getFloat($sName, $bDefault = false)
- {
- if (!isset($this->aParams[$sName]) || is_array($this->aParams[$sName])) {
- return $bDefault;
- }
-
- if (!preg_match('/^[+-]?[0-9]*\.?[0-9]+$/', $this->aParams[$sName])) {
- userError("Floating-point number expected for parameter '$sName'");
- }
-
- return (float) $this->aParams[$sName];
- }
-
- public function getString($sName, $bDefault = false)
- {
- if (!isset($this->aParams[$sName])
- || !is_string($this->aParams[$sName])
- || strlen($this->aParams[$sName]) == 0
- ) {
- return $bDefault;
- }
-
- return $this->aParams[$sName];
- }
-
- public function getSet($sName, $aValues, $sDefault = false)
- {
- if (!isset($this->aParams[$sName])
- || !is_string($this->aParams[$sName])
- || strlen($this->aParams[$sName]) == 0
- ) {
- return $sDefault;
- }
-
- if (!in_array($this->aParams[$sName], $aValues, true)) {
- userError("Parameter '$sName' must be one of: ".join(', ', $aValues));
- }
-
- return $this->aParams[$sName];
- }
-
- public function getStringList($sName, $aDefault = false)
- {
- $sValue = $this->getString($sName);
-
- if ($sValue) {
- // removes all NULL, FALSE and Empty Strings but leaves 0 (zero) values
- return array_values(array_filter(explode(',', $sValue), 'strlen'));
- }
-
- return $aDefault;
- }
-
- public function getPreferredLanguages($sFallback = null)
- {
- if ($sFallback === null && isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
- $sFallback = $_SERVER['HTTP_ACCEPT_LANGUAGE'];
- }
-
- $aLanguages = array();
- $sLangString = $this->getString('accept-language', $sFallback);
-
- if ($sLangString
- && preg_match_all('/(([a-z]{1,8})([-_][a-z]{1,8})?)\s*(;\s*q\s*=\s*(1|0\.[0-9]+))?/i', $sLangString, $aLanguagesParse, PREG_SET_ORDER)
- ) {
- foreach ($aLanguagesParse as $iLang => $aLanguage) {
- $aLanguages[$aLanguage[1]] = isset($aLanguage[5])?(float)$aLanguage[5]:1 - ($iLang/100);
- if (!isset($aLanguages[$aLanguage[2]])) {
- $aLanguages[$aLanguage[2]] = $aLanguages[$aLanguage[1]]/10;
- }
- }
- arsort($aLanguages);
- }
- if (empty($aLanguages) && CONST_Default_Language) {
- $aLanguages[CONST_Default_Language] = 1;
- }
-
- foreach ($aLanguages as $sLanguage => $fLanguagePref) {
- $this->addNameTag($aLangPrefOrder, 'name:'.$sLanguage);
- }
- $this->addNameTag($aLangPrefOrder, 'name');
- $this->addNameTag($aLangPrefOrder, 'brand');
- foreach ($aLanguages as $sLanguage => $fLanguagePref) {
- $this->addNameTag($aLangPrefOrder, 'official_name:'.$sLanguage);
- $this->addNameTag($aLangPrefOrder, 'short_name:'.$sLanguage);
- }
- $this->addNameTag($aLangPrefOrder, 'official_name');
- $this->addNameTag($aLangPrefOrder, 'short_name');
- $this->addNameTag($aLangPrefOrder, 'ref');
- $this->addNameTag($aLangPrefOrder, 'type');
- return $aLangPrefOrder;
- }
-
- private function addNameTag(&$aLangPrefOrder, $sTag)
- {
- $aLangPrefOrder[$sTag] = $sTag;
- $aLangPrefOrder['_place_'.$sTag] = '_place_'.$sTag;
- }
-
- public function hasSetAny($aParamNames)
- {
- foreach ($aParamNames as $sName) {
- if ($this->getBool($sName)) {
- return true;
- }
- }
-
- return false;
- }
-}
+++ /dev/null
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-namespace Nominatim;
-
-/**
- * Segment of a query string.
- *
- * The parts of a query strings are usually separated by commas.
- */
-class Phrase
-{
- // Complete phrase as a string (guaranteed to have no leading or trailing
- // spaces).
- private $sPhrase;
- // Element type for structured searches.
- private $sPhraseType;
- // Possible segmentations of the phrase.
- private $aWordSets;
-
- public function __construct($sPhrase, $sPhraseType)
- {
- $this->sPhrase = trim($sPhrase);
- $this->sPhraseType = $sPhraseType;
- }
-
- /**
- * Get the original phrase of the string.
- */
- public function getPhrase()
- {
- return $this->sPhrase;
- }
-
- /**
- * Return the element type of the phrase.
- *
- * @return string Pharse type if the phrase comes from a structured query
- * or empty string otherwise.
- */
- public function getPhraseType()
- {
- return $this->sPhraseType;
- }
-
- public function setWordSets($aWordSets)
- {
- $this->aWordSets = $aWordSets;
- }
-
- /**
- * Return the array of possible segmentations of the phrase.
- *
- * @return string[][] Array of segmentations, each consisting of an
- * array of terms.
- */
- public function getWordSets()
- {
- return $this->aWordSets;
- }
-
- /**
- * Invert the set of possible segmentations.
- *
- * @return void
- */
- public function invertWordSets()
- {
- foreach ($this->aWordSets as $i => $aSet) {
- $this->aWordSets[$i] = array_reverse($aSet);
- }
- }
-
- public function debugInfo()
- {
- return array(
- 'Type' => $this->sPhraseType,
- 'Phrase' => $this->sPhrase,
- 'WordSets' => $this->aWordSets
- );
- }
-}
+++ /dev/null
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-namespace Nominatim;
-
-require_once(CONST_LibDir.'/AddressDetails.php');
-require_once(CONST_LibDir.'/Result.php');
-
-class PlaceLookup
-{
- protected $oDB;
-
- protected $aLangPrefOrderSql = "''";
-
- protected $bAddressDetails = false;
- protected $bExtraTags = false;
- protected $bNameDetails = false;
-
- protected $bIncludePolygonAsText = false;
- protected $bIncludePolygonAsGeoJSON = false;
- protected $bIncludePolygonAsKML = false;
- protected $bIncludePolygonAsSVG = false;
- protected $fPolygonSimplificationThreshold = 0.0;
-
- protected $sAnchorSql = null;
- protected $sAddressRankListSql = null;
- protected $sAllowedTypesSQLList = null;
- protected $bDeDupe = true;
-
-
- public function __construct(&$oDB)
- {
- $this->oDB =& $oDB;
- }
-
- public function doDeDupe()
- {
- return $this->bDeDupe;
- }
-
- public function setIncludeAddressDetails($b)
- {
- $this->bAddressDetails = $b;
- }
-
- public function loadParamArray($oParams, $sGeomType = null)
- {
- $aLangs = $oParams->getPreferredLanguages();
- $this->aLangPrefOrderSql =
- 'ARRAY['.join(',', $this->oDB->getDBQuotedList($aLangs)).']';
-
- $this->bExtraTags = $oParams->getBool('extratags', false);
- $this->bNameDetails = $oParams->getBool('namedetails', false);
-
- $this->bDeDupe = $oParams->getBool('dedupe', $this->bDeDupe);
-
- if ($sGeomType === null || $sGeomType == 'geojson') {
- $this->bIncludePolygonAsGeoJSON = $oParams->getBool('polygon_geojson');
- }
-
- if ($oParams->getString('format', '') !== 'geojson') {
- if ($sGeomType === null || $sGeomType == 'text') {
- $this->bIncludePolygonAsText = $oParams->getBool('polygon_text');
- }
- if ($sGeomType === null || $sGeomType == 'kml') {
- $this->bIncludePolygonAsKML = $oParams->getBool('polygon_kml');
- }
- if ($sGeomType === null || $sGeomType == 'svg') {
- $this->bIncludePolygonAsSVG = $oParams->getBool('polygon_svg');
- }
- }
- $this->fPolygonSimplificationThreshold
- = $oParams->getFloat('polygon_threshold', 0.0);
-
- $iWantedTypes =
- ($this->bIncludePolygonAsText ? 1 : 0) +
- ($this->bIncludePolygonAsGeoJSON ? 1 : 0) +
- ($this->bIncludePolygonAsKML ? 1 : 0) +
- ($this->bIncludePolygonAsSVG ? 1 : 0);
- if ($iWantedTypes > CONST_PolygonOutput_MaximumTypes) {
- if (CONST_PolygonOutput_MaximumTypes) {
- userError('Select only '.CONST_PolygonOutput_MaximumTypes.' polygon output option');
- } else {
- userError('Polygon output is disabled');
- }
- }
- }
-
- public function getMoreUrlParams()
- {
- $aParams = array();
-
- if ($this->bAddressDetails) {
- $aParams['addressdetails'] = '1';
- }
- if ($this->bExtraTags) {
- $aParams['extratags'] = '1';
- }
- if ($this->bNameDetails) {
- $aParams['namedetails'] = '1';
- }
-
- if ($this->bIncludePolygonAsText) {
- $aParams['polygon_text'] = '1';
- }
- if ($this->bIncludePolygonAsGeoJSON) {
- $aParams['polygon_geojson'] = '1';
- }
- if ($this->bIncludePolygonAsKML) {
- $aParams['polygon_kml'] = '1';
- }
- if ($this->bIncludePolygonAsSVG) {
- $aParams['polygon_svg'] = '1';
- }
-
- if ($this->fPolygonSimplificationThreshold > 0.0) {
- $aParams['polygon_threshold'] = $this->fPolygonSimplificationThreshold;
- }
-
- if (!$this->bDeDupe) {
- $aParams['dedupe'] = '0';
- }
-
- return $aParams;
- }
-
- public function setAnchorSql($sPoint)
- {
- $this->sAnchorSql = $sPoint;
- }
-
- public function setAddressRankList($aList)
- {
- $this->sAddressRankListSql = '('.join(',', $aList).')';
- }
-
- public function setAllowedTypesSQLList($sSql)
- {
- $this->sAllowedTypesSQLList = $sSql;
- }
-
- public function setLanguagePreference($aLangPrefOrder)
- {
- $this->aLangPrefOrderSql = $this->oDB->getArraySQL(
- $this->oDB->getDBQuotedList($aLangPrefOrder)
- );
- }
-
- private function addressImportanceSql($sGeometry, $sPlaceId)
- {
- if ($this->sAnchorSql) {
- $sSQL = 'ST_Distance('.$this->sAnchorSql.','.$sGeometry.')';
- } else {
- $sSQL = '(SELECT max(ai_p.importance * (ai_p.rank_address + 2))';
- $sSQL .= ' FROM place_addressline ai_s, placex ai_p';
- $sSQL .= ' WHERE ai_s.place_id = '.$sPlaceId;
- $sSQL .= ' AND ai_p.place_id = ai_s.address_place_id ';
- $sSQL .= ' AND ai_s.isaddress ';
- $sSQL .= ' AND ai_p.importance is not null)';
- }
-
- return $sSQL.' AS addressimportance,';
- }
-
- private function langAddressSql($sHousenumber)
- {
- if ($this->bAddressDetails) {
- return ''; // langaddress will be computed from address details
- }
-
- return 'get_address_by_language(place_id,'.$sHousenumber.','.$this->aLangPrefOrderSql.') AS langaddress,';
- }
-
- public function lookupOSMID($sType, $iID)
- {
- $sSQL = 'select place_id from placex where osm_type = :type and osm_id = :id';
- $iPlaceID = $this->oDB->getOne($sSQL, array(':type' => $sType, ':id' => $iID));
-
- if (!$iPlaceID) {
- return null;
- }
-
- $aResults = $this->lookup(array($iPlaceID => new Result($iPlaceID)), 0, 30, true);
-
- return empty($aResults) ? null : reset($aResults);
- }
-
- public function lookup($aResults, $iMinRank = 0, $iMaxRank = 30, $bAllowLinked = false)
- {
- Debug::newFunction('Place lookup');
-
- if (empty($aResults)) {
- return array();
- }
- $aSubSelects = array();
-
- $sPlaceIDs = Result::joinIdsByTable($aResults, Result::TABLE_PLACEX);
- if ($sPlaceIDs) {
- Debug::printVar('Ids from placex', $sPlaceIDs);
- $sSQL = 'SELECT ';
- $sSQL .= ' osm_type,';
- $sSQL .= ' osm_id,';
- $sSQL .= ' class,';
- $sSQL .= ' type,';
- $sSQL .= ' admin_level,';
- $sSQL .= ' rank_search,';
- $sSQL .= ' rank_address,';
- $sSQL .= ' min(place_id) AS place_id,';
- $sSQL .= ' min(parent_place_id) AS parent_place_id,';
- $sSQL .= ' -1 as housenumber,';
- $sSQL .= ' country_code,';
- $sSQL .= $this->langAddressSql('-1');
- $sSQL .= ' get_name_by_language(name,'.$this->aLangPrefOrderSql.') AS placename,';
- $sSQL .= " get_name_by_language(name, ARRAY['ref']) AS ref,";
- if ($this->bExtraTags) {
- $sSQL .= 'hstore_to_json(extratags)::text AS extra,';
- }
- if ($this->bNameDetails) {
- $sSQL .= 'hstore_to_json(name)::text AS names,';
- }
- $sSQL .= ' avg(ST_X(centroid)) AS lon, ';
- $sSQL .= ' avg(ST_Y(centroid)) AS lat, ';
- $sSQL .= ' COALESCE(importance,0.75-(rank_search::float/40)) AS importance, ';
- $sSQL .= $this->addressImportanceSql(
- 'ST_Collect(centroid)',
- 'min(CASE WHEN placex.rank_search < 28 THEN placex.place_id ELSE placex.parent_place_id END)'
- );
- $sSQL .= " COALESCE(extratags->'place', extratags->'linked_place') AS extra_place ";
- $sSQL .= ' FROM placex';
- $sSQL .= " WHERE place_id in ($sPlaceIDs) ";
- $sSQL .= ' AND (';
- $sSQL .= " placex.rank_address between $iMinRank and $iMaxRank ";
- if (14 >= $iMinRank && 14 <= $iMaxRank) {
- $sSQL .= " OR (extratags->'place') = 'city'";
- }
- if ($this->sAddressRankListSql) {
- $sSQL .= ' OR placex.rank_address in '.$this->sAddressRankListSql;
- }
- $sSQL .= ' ) ';
- if ($this->sAllowedTypesSQLList) {
- $sSQL .= 'AND placex.class in '.$this->sAllowedTypesSQLList;
- }
- if (!$bAllowLinked) {
- $sSQL .= ' AND linked_place_id is null ';
- }
- $sSQL .= ' GROUP BY ';
- $sSQL .= ' osm_type, ';
- $sSQL .= ' osm_id, ';
- $sSQL .= ' class, ';
- $sSQL .= ' type, ';
- $sSQL .= ' admin_level, ';
- $sSQL .= ' rank_search, ';
- $sSQL .= ' rank_address, ';
- $sSQL .= ' housenumber,';
- $sSQL .= ' country_code, ';
- $sSQL .= ' importance, ';
- if (!$this->bDeDupe) {
- $sSQL .= 'place_id,';
- }
- if (!$this->bAddressDetails) {
- $sSQL .= 'langaddress, ';
- }
- $sSQL .= ' placename, ';
- $sSQL .= ' ref, ';
- if ($this->bExtraTags) {
- $sSQL .= 'extratags, ';
- }
- if ($this->bNameDetails) {
- $sSQL .= 'name, ';
- }
- $sSQL .= ' extra_place ';
-
- $aSubSelects[] = $sSQL;
- }
-
- // postcode table
- $sPlaceIDs = Result::joinIdsByTable($aResults, Result::TABLE_POSTCODE);
- if ($sPlaceIDs) {
- Debug::printVar('Ids from location_postcode', $sPlaceIDs);
- $sSQL = 'SELECT';
- $sSQL .= " 'P' as osm_type,";
- $sSQL .= ' (SELECT osm_id from placex p WHERE p.place_id = lp.parent_place_id) as osm_id,';
- $sSQL .= " 'place' as class, 'postcode' as type,";
- $sSQL .= ' null::smallint as admin_level, rank_search, rank_address,';
- $sSQL .= ' place_id, parent_place_id,';
- $sSQL .= ' -1 as housenumber,';
- $sSQL .= ' country_code,';
- $sSQL .= $this->langAddressSql('-1');
- $sSQL .= ' postcode as placename,';
- $sSQL .= ' postcode as ref,';
- if ($this->bExtraTags) {
- $sSQL .= 'null::text AS extra,';
- }
- if ($this->bNameDetails) {
- $sSQL .= 'null::text AS names,';
- }
- $sSQL .= ' ST_x(geometry) AS lon, ST_y(geometry) AS lat,';
- $sSQL .= ' (0.75-(rank_search::float/40)) AS importance, ';
- $sSQL .= $this->addressImportanceSql('geometry', 'lp.parent_place_id');
- $sSQL .= ' null::text AS extra_place ';
- $sSQL .= 'FROM location_postcode lp';
- $sSQL .= " WHERE place_id in ($sPlaceIDs) ";
- $sSQL .= " AND lp.rank_address between $iMinRank and $iMaxRank";
-
- $aSubSelects[] = $sSQL;
- }
-
- // All other tables are rank 30 only.
- if ($iMaxRank == 30) {
- // TIGER table
- if (CONST_Use_US_Tiger_Data) {
- $sPlaceIDs = Result::joinIdsByTable($aResults, Result::TABLE_TIGER);
- if ($sPlaceIDs) {
- Debug::printVar('Ids from Tiger table', $sPlaceIDs);
- $sHousenumbers = Result::sqlHouseNumberTable($aResults, Result::TABLE_TIGER);
- // Tiger search only if a housenumber was searched and if it was found
- // (realized through a join)
- $sSQL = ' SELECT ';
- $sSQL .= " 'T' AS osm_type, ";
- $sSQL .= ' (SELECT osm_id from placex p WHERE p.place_id=blub.parent_place_id) as osm_id, ';
- $sSQL .= " 'place' AS class, ";
- $sSQL .= " 'house' AS type, ";
- $sSQL .= ' null::smallint AS admin_level, ';
- $sSQL .= ' 30 AS rank_search, ';
- $sSQL .= ' 30 AS rank_address, ';
- $sSQL .= ' place_id, ';
- $sSQL .= ' parent_place_id, ';
- $sSQL .= ' housenumber_for_place as housenumber,';
- $sSQL .= " 'us' AS country_code, ";
- $sSQL .= $this->langAddressSql('housenumber_for_place');
- $sSQL .= ' null::text AS placename, ';
- $sSQL .= ' null::text AS ref, ';
- if ($this->bExtraTags) {
- $sSQL .= 'null::text AS extra,';
- }
- if ($this->bNameDetails) {
- $sSQL .= 'null::text AS names,';
- }
- $sSQL .= ' st_x(centroid) AS lon, ';
- $sSQL .= ' st_y(centroid) AS lat,';
- $sSQL .= ' -1.15 AS importance, ';
- $sSQL .= $this->addressImportanceSql('centroid', 'blub.parent_place_id');
- $sSQL .= ' null::text AS extra_place ';
- $sSQL .= ' FROM (';
- $sSQL .= ' SELECT place_id, '; // interpolate the Tiger housenumbers here
- $sSQL .= ' CASE WHEN startnumber != endnumber';
- $sSQL .= ' THEN ST_LineInterpolatePoint(linegeo, (housenumber_for_place-startnumber::float)/(endnumber-startnumber)::float)';
- $sSQL .= ' ELSE ST_LineInterpolatePoint(linegeo, 0.5) END AS centroid, ';
- $sSQL .= ' parent_place_id, ';
- $sSQL .= ' housenumber_for_place';
- $sSQL .= ' FROM (';
- $sSQL .= ' location_property_tiger ';
- $sSQL .= ' JOIN (values '.$sHousenumbers.') AS housenumbers(place_id, housenumber_for_place) USING(place_id)) ';
- $sSQL .= ' WHERE ';
- $sSQL .= ' housenumber_for_place >= startnumber';
- $sSQL .= ' AND housenumber_for_place <= endnumber';
- $sSQL .= ' ) AS blub'; //postgres wants an alias here
-
- $aSubSelects[] = $sSQL;
- }
- }
-
- // osmline - interpolated housenumbers
- $sPlaceIDs = Result::joinIdsByTable($aResults, Result::TABLE_OSMLINE);
- if ($sPlaceIDs) {
- Debug::printVar('Ids from interpolation', $sPlaceIDs);
- $sHousenumbers = Result::sqlHouseNumberTable($aResults, Result::TABLE_OSMLINE);
- // interpolation line search only if a housenumber was searched
- // (realized through a join)
- $sSQL = 'SELECT ';
- $sSQL .= " 'W' AS osm_type, ";
- $sSQL .= ' osm_id, ';
- $sSQL .= " 'place' AS class, ";
- $sSQL .= " 'house' AS type, ";
- $sSQL .= ' null::smallint AS admin_level, ';
- $sSQL .= ' 30 AS rank_search, ';
- $sSQL .= ' 30 AS rank_address, ';
- $sSQL .= ' place_id, ';
- $sSQL .= ' parent_place_id, ';
- $sSQL .= ' housenumber_for_place as housenumber,';
- $sSQL .= ' country_code, ';
- $sSQL .= $this->langAddressSql('housenumber_for_place');
- $sSQL .= ' null::text AS placename, ';
- $sSQL .= ' null::text AS ref, ';
- if ($this->bExtraTags) {
- $sSQL .= 'null::text AS extra, ';
- }
- if ($this->bNameDetails) {
- $sSQL .= 'null::text AS names, ';
- }
- $sSQL .= ' st_x(centroid) AS lon, ';
- $sSQL .= ' st_y(centroid) AS lat, ';
- // slightly smaller than the importance for normal houses
- $sSQL .= ' -0.1 AS importance, ';
- $sSQL .= $this->addressImportanceSql('centroid', 'blub.parent_place_id');
- $sSQL .= ' null::text AS extra_place ';
- $sSQL .= ' FROM (';
- $sSQL .= ' SELECT ';
- $sSQL .= ' osm_id, ';
- $sSQL .= ' place_id, ';
- $sSQL .= ' country_code, ';
- $sSQL .= ' CASE '; // interpolate the housenumbers here
- $sSQL .= ' WHEN startnumber != endnumber ';
- $sSQL .= ' THEN ST_LineInterpolatePoint(linegeo, (housenumber_for_place-startnumber::float)/(endnumber-startnumber)::float) ';
- $sSQL .= ' ELSE linegeo ';
- $sSQL .= ' END as centroid, ';
- $sSQL .= ' parent_place_id, ';
- $sSQL .= ' housenumber_for_place ';
- $sSQL .= ' FROM (';
- $sSQL .= ' location_property_osmline ';
- $sSQL .= ' JOIN (values '.$sHousenumbers.') AS housenumbers(place_id, housenumber_for_place) USING(place_id)';
- $sSQL .= ' ) ';
- $sSQL .= ' WHERE housenumber_for_place >= 0 ';
- $sSQL .= ' ) as blub'; //postgres wants an alias here
-
- $aSubSelects[] = $sSQL;
- }
- }
-
- if (empty($aSubSelects)) {
- return array();
- }
-
- $sSQL = join(' UNION ', $aSubSelects);
- Debug::printSQL($sSQL);
- $aPlaces = $this->oDB->getAll($sSQL, null, 'Could not lookup place');
-
- foreach ($aPlaces as &$aPlace) {
- $aPlace['importance'] = (float) $aPlace['importance'];
- if ($this->bAddressDetails) {
- // to get addressdetails for tiger data, the housenumber is needed
- $aPlace['address'] = new AddressDetails(
- $this->oDB,
- $aPlace['place_id'],
- $aPlace['housenumber'],
- $this->aLangPrefOrderSql
- );
- $aPlace['langaddress'] = $aPlace['address']->getLocaleAddress();
- }
-
- if ($this->bExtraTags) {
- if ($aPlace['extra']) {
- $aPlace['sExtraTags'] = json_decode($aPlace['extra'], true);
- } else {
- $aPlace['sExtraTags'] = (object) array();
- }
- }
-
- if ($this->bNameDetails) {
- $aPlace['sNameDetails'] = $this->extractNames($aPlace['names']);
- }
-
- $aPlace['addresstype'] = ClassTypes\getLabelTag(
- $aPlace,
- $aPlace['country_code']
- );
-
- $aResults[$aPlace['place_id']] = $aPlace;
- }
-
- $aResults = array_filter(
- $aResults,
- function ($v) {
- return !($v instanceof Result);
- }
- );
-
- Debug::printVar('Places', $aResults);
-
- return $aResults;
- }
-
-
- private function extractNames($sNames)
- {
- if (!$sNames) {
- return (object) array();
- }
-
- $aFullNames = json_decode($sNames, true);
- $aNames = array();
-
- foreach ($aFullNames as $sKey => $sValue) {
- if (strpos($sKey, '_place_') === 0) {
- $sSubKey = substr($sKey, 7);
- if (array_key_exists($sSubKey, $aFullNames)) {
- $aNames[$sKey] = $sValue;
- } else {
- $aNames[$sSubKey] = $sValue;
- }
- } else {
- $aNames[$sKey] = $sValue;
- }
- }
-
- return $aNames;
- }
-
-
- /* returns an array which will contain the keys
- * aBoundingBox
- * and may also contain one or more of the keys
- * asgeojson
- * askml
- * assvg
- * astext
- * lat
- * lon
- */
- public function getOutlines($iPlaceID, $fLon = null, $fLat = null, $fRadius = null, $fLonReverse = null, $fLatReverse = null)
- {
-
- $aOutlineResult = array();
- if (!$iPlaceID) {
- return $aOutlineResult;
- }
-
- // Get the bounding box and outline polygon
- $sSQL = 'select place_id,0 as numfeatures,st_area(geometry) as area,';
- $sSQL .= ' ST_Y(centroid) as centrelat, ST_X(centroid) as centrelon,';
- $sSQL .= ' ST_YMin(geometry) as minlat,ST_YMax(geometry) as maxlat,';
- $sSQL .= ' ST_XMin(geometry) as minlon,ST_XMax(geometry) as maxlon';
- if ($this->bIncludePolygonAsGeoJSON) {
- $sSQL .= ',ST_AsGeoJSON(geometry) as asgeojson';
- }
- if ($this->bIncludePolygonAsKML) {
- $sSQL .= ',ST_AsKML(geometry) as askml';
- }
- if ($this->bIncludePolygonAsSVG) {
- $sSQL .= ',ST_AsSVG(geometry) as assvg';
- }
- if ($this->bIncludePolygonAsText) {
- $sSQL .= ',ST_AsText(geometry) as astext';
- }
-
- $sSQL .= ' FROM (SELECT place_id';
- if ($fLonReverse != null && $fLatReverse != null) {
- $sSQL .= ',CASE WHEN (class = \'highway\') AND (ST_GeometryType(geometry) = \'ST_LineString\') THEN ';
- $sSQL .=' ST_ClosestPoint(geometry, ST_SetSRID(ST_Point('.$fLatReverse.','.$fLonReverse.'),4326))';
- $sSQL .=' ELSE centroid END AS centroid';
- } else {
- $sSQL .= ',centroid';
- }
- if ($this->fPolygonSimplificationThreshold > 0) {
- $sSQL .= ',ST_SimplifyPreserveTopology(geometry,'.$this->fPolygonSimplificationThreshold.') as geometry';
- } else {
- $sSQL .= ',geometry';
- }
- $sSQL .= ' FROM placex where place_id = '.$iPlaceID.') as plx';
-
- $aPointPolygon = $this->oDB->getRow($sSQL, null, 'Could not get outline');
-
- if ($aPointPolygon && $aPointPolygon['place_id']) {
- if ($aPointPolygon['centrelon'] !== null && $aPointPolygon['centrelat'] !== null) {
- $aOutlineResult['lat'] = $aPointPolygon['centrelat'];
- $aOutlineResult['lon'] = $aPointPolygon['centrelon'];
- }
-
- if ($this->bIncludePolygonAsGeoJSON) {
- $aOutlineResult['asgeojson'] = $aPointPolygon['asgeojson'];
- }
- if ($this->bIncludePolygonAsKML) {
- $aOutlineResult['askml'] = $aPointPolygon['askml'];
- }
- if ($this->bIncludePolygonAsSVG) {
- $aOutlineResult['assvg'] = $aPointPolygon['assvg'];
- }
- if ($this->bIncludePolygonAsText) {
- $aOutlineResult['astext'] = $aPointPolygon['astext'];
- }
-
- if (abs($aPointPolygon['minlat'] - $aPointPolygon['maxlat']) < 0.0000001) {
- $aPointPolygon['minlat'] = $aPointPolygon['minlat'] - $fRadius;
- $aPointPolygon['maxlat'] = $aPointPolygon['maxlat'] + $fRadius;
- }
-
- if (abs($aPointPolygon['minlon'] - $aPointPolygon['maxlon']) < 0.0000001) {
- $aPointPolygon['minlon'] = $aPointPolygon['minlon'] - $fRadius;
- $aPointPolygon['maxlon'] = $aPointPolygon['maxlon'] + $fRadius;
- }
-
- $aOutlineResult['aBoundingBox'] = array(
- (string)$aPointPolygon['minlat'],
- (string)$aPointPolygon['maxlat'],
- (string)$aPointPolygon['minlon'],
- (string)$aPointPolygon['maxlon']
- );
- }
-
- // as a fallback we generate a bounding box without knowing the size of the geometry
- if ((!isset($aOutlineResult['aBoundingBox'])) && isset($fLon)) {
- $aBounds = array(
- 'minlat' => $fLat - $fRadius,
- 'maxlat' => $fLat + $fRadius,
- 'minlon' => $fLon - $fRadius,
- 'maxlon' => $fLon + $fRadius
- );
-
- $aOutlineResult['aBoundingBox'] = array(
- (string)$aBounds['minlat'],
- (string)$aBounds['maxlat'],
- (string)$aBounds['minlon'],
- (string)$aBounds['maxlon']
- );
- }
- return $aOutlineResult;
- }
-}
+++ /dev/null
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-namespace Nominatim;
-
-/**
- * A single result of a search operation or a reverse lookup.
- *
- * This object only contains the id of the result. It does not yet
- * have any details needed to format the output document.
- */
-class Result
-{
- const TABLE_PLACEX = 0;
- const TABLE_POSTCODE = 1;
- const TABLE_OSMLINE = 2;
- const TABLE_TIGER = 3;
-
- /// Database table that contains the result.
- public $iTable;
- /// Id of the result.
- public $iId;
- /// House number (only for interpolation results).
- public $iHouseNumber = -1;
- /// Number of exact matches in address (address searches only).
- public $iExactMatches = 0;
- /// Subranking within the results (the higher the worse).
- public $iResultRank = 0;
- /// Address rank of the result.
- public $iAddressRank;
-
- public function debugInfo()
- {
- return array(
- 'Table' => $this->iTable,
- 'ID' => $this->iId,
- 'House number' => $this->iHouseNumber,
- 'Exact Matches' => $this->iExactMatches,
- 'Result rank' => $this->iResultRank
- );
- }
-
-
- public function __construct($sId, $iTable = Result::TABLE_PLACEX)
- {
- $this->iTable = $iTable;
- $this->iId = (int) $sId;
- }
-
- public static function joinIdsByTable($aResults, $iTable)
- {
- return join(',', array_keys(array_filter(
- $aResults,
- function ($aValue) use ($iTable) {
- return $aValue->iTable == $iTable;
- }
- )));
- }
-
- public static function joinIdsByTableMinRank($aResults, $iTable, $iMinAddressRank)
- {
- return join(',', array_keys(array_filter(
- $aResults,
- function ($aValue) use ($iTable, $iMinAddressRank) {
- return $aValue->iTable == $iTable && $aValue->iAddressRank >= $iMinAddressRank;
- }
- )));
- }
-
- public static function joinIdsByTableMaxRank($aResults, $iTable, $iMaxAddressRank)
- {
- return join(',', array_keys(array_filter(
- $aResults,
- function ($aValue) use ($iTable, $iMaxAddressRank) {
- return $aValue->iTable == $iTable && $aValue->iAddressRank <= $iMaxAddressRank;
- }
- )));
- }
-
- public static function sqlHouseNumberTable($aResults, $iTable)
- {
- $sHousenumbers = '';
- $sSep = '';
- foreach ($aResults as $oResult) {
- if ($oResult->iTable == $iTable) {
- $sHousenumbers .= $sSep.'('.$oResult->iId.',';
- $sHousenumbers .= $oResult->iHouseNumber.')';
- $sSep = ',';
- }
- }
-
- return $sHousenumbers;
- }
-
- /**
- * Split a result array into highest ranked result and the rest
- *
- * @param object[] $aResults List of results to split.
- *
- * @return array[]
- */
- public static function splitResults($aResults)
- {
- $aHead = array();
- $aTail = array();
- $iMinRank = 10000;
-
- foreach ($aResults as $oRes) {
- if ($oRes->iResultRank < $iMinRank) {
- $aTail += $aHead;
- $aHead = array($oRes->iId => $oRes);
- $iMinRank = $oRes->iResultRank;
- } elseif ($oRes->iResultRank == $iMinRank) {
- $aHead[$oRes->iId] = $oRes;
- } else {
- $aTail[$oRes->iId] = $oRes;
- }
- }
-
- return array('head' => $aHead, 'tail' => $aTail);
- }
-}
+++ /dev/null
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-namespace Nominatim;
-
-require_once(CONST_LibDir.'/Result.php');
-
-class ReverseGeocode
-{
- protected $oDB;
- protected $iMaxRank = 28;
-
-
- public function __construct(&$oDB)
- {
- $this->oDB =& $oDB;
- }
-
-
- public function setZoom($iZoom)
- {
- // Zoom to rank, this could probably be calculated but a lookup gives fine control
- $aZoomRank = array(
- 0 => 2, // Continent / Sea
- 1 => 2,
- 2 => 2,
- 3 => 4, // Country
- 4 => 4,
- 5 => 8, // State
- 6 => 10, // Region
- 7 => 10,
- 8 => 12, // County
- 9 => 12,
- 10 => 17, // City
- 11 => 17,
- 12 => 18, // Town
- 13 => 19, // Village
- 14 => 22, // Neighbourhood
- 15 => 25, // Locality
- 16 => 26, // major street
- 17 => 27, // minor street
- 18 => 30, // or >, Building
- 19 => 30, // or >, Building
- );
- $this->iMaxRank = (isset($iZoom) && isset($aZoomRank[$iZoom]))?$aZoomRank[$iZoom]:28;
- }
-
- /**
- * Find the closest interpolation with the given search diameter.
- *
- * @param string $sPointSQL Reverse geocoding point as SQL
- * @param float $fSearchDiam Search diameter
- *
- * @return Record of the interpolation or null.
- */
- protected function lookupInterpolation($sPointSQL, $fSearchDiam)
- {
- Debug::newFunction('lookupInterpolation');
- $sSQL = 'SELECT place_id, parent_place_id, 30 as rank_search,';
- $sSQL .= ' (CASE WHEN endnumber != startnumber';
- $sSQL .= ' THEN (endnumber - startnumber) * ST_LineLocatePoint(linegeo,'.$sPointSQL.')';
- $sSQL .= ' ELSE startnumber END) as fhnr,';
- $sSQL .= ' startnumber, endnumber, step,';
- $sSQL .= ' ST_Distance(linegeo,'.$sPointSQL.') as distance';
- $sSQL .= ' FROM location_property_osmline';
- $sSQL .= ' WHERE ST_DWithin('.$sPointSQL.', linegeo, '.$fSearchDiam.')';
- $sSQL .= ' and indexed_status = 0 and startnumber is not NULL ';
- $sSQL .= ' and parent_place_id != 0';
- $sSQL .= ' ORDER BY distance ASC limit 1';
- Debug::printSQL($sSQL);
-
- return $this->oDB->getRow(
- $sSQL,
- null,
- 'Could not determine closest housenumber on an osm interpolation line.'
- );
- }
-
- protected function lookupLargeArea($sPointSQL, $iMaxRank)
- {
- $sCountryCode = $this->getCountryCode($sPointSQL);
- if (CONST_Search_WithinCountries and $sCountryCode == null) {
- return null;
- }
-
- if ($iMaxRank > 4) {
- $aPlace = $this->lookupPolygon($sPointSQL, $iMaxRank);
- if ($aPlace) {
- return new Result($aPlace['place_id']);
- }
- }
-
- // If no polygon which contains the searchpoint is found,
- // searches in the country_osm_grid table for a polygon.
- return $this->lookupInCountry($sPointSQL, $iMaxRank, $sCountryCode);
- }
-
- protected function getCountryCode($sPointSQL)
- {
- Debug::newFunction('getCountryCode');
- // searches for polygon in table country_osm_grid which contains the searchpoint
- // and searches for the nearest place node to the searchpoint in this polygon
- $sSQL = 'SELECT country_code FROM country_osm_grid';
- $sSQL .= ' WHERE ST_CONTAINS(geometry, '.$sPointSQL.') LIMIT 1';
- Debug::printSQL($sSQL);
-
- $sCountryCode = $this->oDB->getOne(
- $sSQL,
- null,
- 'Could not determine country polygon containing the point.'
- );
- return $sCountryCode;
- }
-
- protected function lookupInCountry($sPointSQL, $iMaxRank, $sCountryCode)
- {
- Debug::newFunction('lookupInCountry');
- if ($sCountryCode) {
- if ($iMaxRank > 4) {
- // look for place nodes with the given country code
- $sSQL = 'SELECT place_id FROM';
- $sSQL .= ' (SELECT place_id, rank_search,';
- $sSQL .= ' ST_distance('.$sPointSQL.', geometry) as distance';
- $sSQL .= ' FROM placex';
- $sSQL .= ' WHERE osm_type = \'N\'';
- $sSQL .= ' AND country_code = \''.$sCountryCode.'\'';
- $sSQL .= ' AND rank_address between 4 and 25'; // needed to select right index
- $sSQL .= ' AND rank_search between 5 and ' .min(25, $iMaxRank);
- $sSQL .= ' AND type != \'postcode\'';
- $sSQL .= ' AND name IS NOT NULL ';
- $sSQL .= ' and indexed_status = 0 and linked_place_id is null';
- $sSQL .= ' AND ST_Buffer(geometry, reverse_place_diameter(rank_search)) && '.$sPointSQL;
- $sSQL .= ') as a ';
- $sSQL .= 'WHERE distance <= reverse_place_diameter(rank_search)';
- $sSQL .= ' ORDER BY rank_search DESC, distance ASC';
- $sSQL .= ' LIMIT 1';
- Debug::printSQL($sSQL);
-
- $aPlace = $this->oDB->getRow($sSQL, null, 'Could not determine place node.');
- Debug::printVar('Country node', $aPlace);
-
- if ($aPlace) {
- return new Result($aPlace['place_id']);
- }
- }
-
- // still nothing, then return the country object
- $sSQL = 'SELECT place_id, ST_distance('.$sPointSQL.', centroid) as distance';
- $sSQL .= ' FROM placex';
- $sSQL .= ' WHERE country_code = \''.$sCountryCode.'\'';
- $sSQL .= ' AND rank_search = 4 AND rank_address = 4';
- $sSQL .= ' AND class in (\'boundary\', \'place\')';
- $sSQL .= ' AND linked_place_id is null';
- $sSQL .= ' ORDER BY distance ASC';
- Debug::printSQL($sSQL);
-
- $aPlace = $this->oDB->getRow($sSQL, null, 'Could not determine place node.');
- Debug::printVar('Country place', $aPlace);
- if ($aPlace) {
- return new Result($aPlace['place_id']);
- }
- }
-
- return null;
- }
-
- /**
- * Search for areas or nodes for areas or nodes between state and suburb level.
- *
- * @param string $sPointSQL Search point as SQL string.
- * @param int $iMaxRank Maximum address rank of the feature.
- *
- * @return Record of the found feature or null.
- *
- * Searches first for polygon that contains the search point.
- * If such a polygon is found, place nodes with a higher rank are
- * searched inside the polygon.
- */
- protected function lookupPolygon($sPointSQL, $iMaxRank)
- {
- Debug::newFunction('lookupPolygon');
- // polygon search begins at suburb-level
- if ($iMaxRank > 25) {
- $iMaxRank = 25;
- }
- // no polygon search over country-level
- if ($iMaxRank < 5) {
- $iMaxRank = 5;
- }
- // search for polygon
- $sSQL = 'SELECT place_id, parent_place_id, rank_address, rank_search FROM';
- $sSQL .= '(select place_id, parent_place_id, rank_address, rank_search, country_code, geometry';
- $sSQL .= ' FROM placex';
- $sSQL .= ' WHERE ST_GeometryType(geometry) in (\'ST_Polygon\', \'ST_MultiPolygon\')';
- // Ensure that query planner doesn't use the index on rank_search.
- $sSQL .= ' AND coalesce(rank_search, 0) between 5 and ' .$iMaxRank;
- $sSQL .= ' AND rank_address between 4 and 25'; // needed for index selection
- $sSQL .= ' AND geometry && '.$sPointSQL;
- $sSQL .= ' AND type != \'postcode\' ';
- $sSQL .= ' AND name is not null';
- $sSQL .= ' AND indexed_status = 0 and linked_place_id is null';
- $sSQL .= ' ORDER BY rank_search DESC LIMIT 50 ) as a';
- $sSQL .= ' WHERE ST_Contains(geometry, '.$sPointSQL.' )';
- $sSQL .= ' ORDER BY rank_search DESC LIMIT 1';
- Debug::printSQL($sSQL);
-
- $aPoly = $this->oDB->getRow($sSQL, null, 'Could not determine polygon containing the point.');
- Debug::printVar('Polygon result', $aPoly);
-
- if ($aPoly) {
- // if a polygon is found, search for placenodes begins ...
- $iRankAddress = $aPoly['rank_address'];
- $iRankSearch = $aPoly['rank_search'];
- $iPlaceID = $aPoly['place_id'];
-
- if ($iRankSearch != $iMaxRank) {
- $sSQL = 'SELECT place_id FROM ';
- $sSQL .= '(SELECT place_id, rank_search, country_code, geometry,';
- $sSQL .= ' ST_distance('.$sPointSQL.', geometry) as distance';
- $sSQL .= ' FROM placex';
- $sSQL .= ' WHERE osm_type = \'N\'';
- $sSQL .= ' AND rank_search > '.$iRankSearch;
- $sSQL .= ' AND rank_search <= '.$iMaxRank;
- $sSQL .= ' AND rank_address between 4 and 25'; // needed to select right index
- $sSQL .= ' AND type != \'postcode\'';
- $sSQL .= ' AND name IS NOT NULL ';
- $sSQL .= ' AND indexed_status = 0 AND linked_place_id is null';
- $sSQL .= ' AND ST_Buffer(geometry, reverse_place_diameter(rank_search)) && '.$sPointSQL;
- $sSQL .= ' ORDER BY rank_search DESC, distance ASC';
- $sSQL .= ' limit 100) as a';
- $sSQL .= ' WHERE ST_Contains((SELECT geometry FROM placex WHERE place_id = '.$iPlaceID.'), geometry )';
- $sSQL .= ' AND distance <= reverse_place_diameter(rank_search)';
- $sSQL .= ' ORDER BY rank_search DESC, distance ASC';
- $sSQL .= ' LIMIT 1';
- Debug::printSQL($sSQL);
-
- $aPlaceNode = $this->oDB->getRow($sSQL, null, 'Could not determine place node.');
- Debug::printVar('Nearest place node', $aPlaceNode);
- if ($aPlaceNode) {
- return $aPlaceNode;
- }
- }
- }
- return $aPoly;
- }
-
-
- public function lookup($fLat, $fLon, $bDoInterpolation = true)
- {
- return $this->lookupPoint(
- 'ST_SetSRID(ST_Point('.$fLon.','.$fLat.'),4326)',
- $bDoInterpolation
- );
- }
-
- public function lookupPoint($sPointSQL, $bDoInterpolation = true)
- {
- Debug::newFunction('lookupPoint');
- // Find the nearest point
- $fSearchDiam = 0.006;
- $oResult = null;
- $aPlace = null;
-
- // for POI or street level
- if ($this->iMaxRank >= 26) {
- // starts if the search is on POI or street level,
- // searches for the nearest POI or street,
- // if a street is found and a POI is searched for,
- // the nearest POI which the found street is a parent of is chosen.
- $sSQL = 'select place_id,parent_place_id,rank_address,country_code,';
- $sSQL .= ' ST_distance('.$sPointSQL.', geometry) as distance';
- $sSQL .= ' FROM ';
- $sSQL .= ' placex';
- $sSQL .= ' WHERE ST_DWithin('.$sPointSQL.', geometry, '.$fSearchDiam.')';
- $sSQL .= ' AND';
- $sSQL .= ' rank_address between 26 and '.$this->iMaxRank;
- $sSQL .= ' and (name is not null or housenumber is not null';
- $sSQL .= ' or rank_address between 26 and 27)';
- $sSQL .= ' and (rank_address between 26 and 27';
- $sSQL .= ' or ST_GeometryType(geometry) != \'ST_LineString\')';
- $sSQL .= ' and class not in (\'boundary\')';
- $sSQL .= ' and indexed_status = 0 and linked_place_id is null';
- $sSQL .= ' and (ST_GeometryType(geometry) not in (\'ST_Polygon\',\'ST_MultiPolygon\') ';
- $sSQL .= ' OR ST_DWithin('.$sPointSQL.', centroid, '.$fSearchDiam.'))';
- $sSQL .= ' ORDER BY distance ASC limit 1';
- Debug::printSQL($sSQL);
-
- $aPlace = $this->oDB->getRow($sSQL, null, 'Could not determine closest place.');
-
- Debug::printVar('POI/street level result', $aPlace);
- if ($aPlace) {
- $iPlaceID = $aPlace['place_id'];
- $oResult = new Result($iPlaceID);
- $iRankAddress = $aPlace['rank_address'];
- }
-
- if ($aPlace) {
- // if street and maxrank > streetlevel
- if ($iRankAddress <= 27 && $this->iMaxRank > 27) {
- // find the closest object (up to a certain radius) of which the street is a parent of
- $sSQL = ' select place_id,';
- $sSQL .= ' ST_distance('.$sPointSQL.', geometry) as distance';
- $sSQL .= ' FROM ';
- $sSQL .= ' placex';
- // radius ?
- $sSQL .= ' WHERE ST_DWithin('.$sPointSQL.', geometry, 0.001)';
- $sSQL .= ' AND parent_place_id = '.$iPlaceID;
- $sSQL .= ' and rank_address > 28';
- $sSQL .= ' and ST_GeometryType(geometry) != \'ST_LineString\'';
- $sSQL .= ' and (name is not null or housenumber is not null)';
- $sSQL .= ' and class not in (\'boundary\')';
- $sSQL .= ' and indexed_status = 0 and linked_place_id is null';
- $sSQL .= ' ORDER BY distance ASC limit 1';
- Debug::printSQL($sSQL);
-
- $aStreet = $this->oDB->getRow($sSQL, null, 'Could not determine closest place.');
- Debug::printVar('Closest POI result', $aStreet);
-
- if ($aStreet) {
- $aPlace = $aStreet;
- $oResult = new Result($aStreet['place_id']);
- $iRankAddress = 30;
- }
- }
-
- // In the US we can check TIGER data for nearest housenumber
- if (CONST_Use_US_Tiger_Data
- && $iRankAddress <= 27
- && $aPlace['country_code'] == 'us'
- && $this->iMaxRank >= 28
- ) {
- $sSQL = 'SELECT place_id,parent_place_id,30 as rank_search,';
- $sSQL .= ' (endnumber - startnumber) * ST_LineLocatePoint(linegeo,'.$sPointSQL.') as fhnr,';
- $sSQL .= ' startnumber, endnumber, step,';
- $sSQL .= ' ST_Distance('.$sPointSQL.', linegeo) as distance';
- $sSQL .= ' FROM location_property_tiger WHERE parent_place_id = '.$oResult->iId;
- $sSQL .= ' AND ST_DWithin('.$sPointSQL.', linegeo, 0.001)';
- $sSQL .= ' ORDER BY distance ASC limit 1';
- Debug::printSQL($sSQL);
-
- $aPlaceTiger = $this->oDB->getRow($sSQL, null, 'Could not determine closest Tiger place.');
- Debug::printVar('Tiger house number result', $aPlaceTiger);
-
- if ($aPlaceTiger) {
- $aPlace = $aPlaceTiger;
- $oResult = new Result($aPlaceTiger['place_id'], Result::TABLE_TIGER);
- $iRndNum = max(0, round($aPlaceTiger['fhnr'] / $aPlaceTiger['step']) * $aPlaceTiger['step']);
- $oResult->iHouseNumber = $aPlaceTiger['startnumber'] + $iRndNum;
- if ($oResult->iHouseNumber > $aPlaceTiger['endnumber']) {
- $oResult->iHouseNumber = $aPlaceTiger['endnumber'];
- }
- $iRankAddress = 30;
- }
- }
- }
-
- if ($bDoInterpolation && $this->iMaxRank >= 30) {
- $fDistance = $fSearchDiam;
- if ($aPlace) {
- // We can't reliably go from the closest street to an
- // interpolation line because the closest interpolation
- // may have a different street segments as a parent.
- // Therefore allow an interpolation line to take precedence
- // even when the street is closer.
- $fDistance = $iRankAddress < 28 ? 0.001 : $aPlace['distance'];
- }
-
- $aHouse = $this->lookupInterpolation($sPointSQL, $fDistance);
- Debug::printVar('Interpolation result', $aPlace);
-
- if ($aHouse) {
- $oResult = new Result($aHouse['place_id'], Result::TABLE_OSMLINE);
- $iRndNum = max(0, round($aHouse['fhnr'] / $aHouse['step']) * $aHouse['step']);
- $oResult->iHouseNumber = $aHouse['startnumber'] + $iRndNum;
- if ($oResult->iHouseNumber > $aHouse['endnumber']) {
- $oResult->iHouseNumber = $aHouse['endnumber'];
- }
- $aPlace = $aHouse;
- }
- }
-
- if (!$aPlace) {
- // if no POI or street is found ...
- $oResult = $this->lookupLargeArea($sPointSQL, 25);
- }
- } else {
- // lower than street level ($iMaxRank < 26 )
- $oResult = $this->lookupLargeArea($sPointSQL, $this->iMaxRank);
- }
-
- Debug::printVar('Final result', $oResult);
- return $oResult;
- }
-}
+++ /dev/null
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-namespace Nominatim;
-
-require_once(CONST_LibDir.'/lib.php');
-
-
-/**
- * Collection of search constraints that are independent of the
- * actual interpretation of the search query.
- *
- * The search context is shared between all SearchDescriptions. This
- * object mainly serves as context provider for the database queries.
- * Therefore most data is directly cached as SQL statements.
- */
-class SearchContext
-{
- /// Search radius around a given Near reference point.
- private $fNearRadius = false;
- /// True if search must be restricted to viewbox only.
- public $bViewboxBounded = false;
-
- /// Reference point for search (as SQL).
- public $sqlNear = '';
- /// Viewbox selected for search (as SQL).
- public $sqlViewboxSmall = '';
- /// Viewbox with a larger buffer around (as SQL).
- public $sqlViewboxLarge = '';
- /// Reference along a route (as SQL).
- public $sqlViewboxCentre = '';
- /// List of countries to restrict search to (as array).
- public $aCountryList = null;
- /// List of countries to restrict search to (as SQL).
- public $sqlCountryList = '';
- /// List of place IDs to exclude (as SQL).
- private $sqlExcludeList = '';
- /// Subset of word ids of full words in the query.
- private $aFullNameWords = array();
-
- public function setFullNameWords($aWordList)
- {
- $this->aFullNameWords = $aWordList;
- }
-
- public function getFullNameTerms()
- {
- return $this->aFullNameWords;
- }
-
- /**
- * Check if a reference point is defined.
- *
- * @return bool True if a reference point is defined.
- */
- public function hasNearPoint()
- {
- return $this->fNearRadius !== false;
- }
-
- /**
- * Get radius around reference point.
- *
- * @return float Search radius around reference point.
- */
- public function nearRadius()
- {
- return $this->fNearRadius;
- }
-
- /**
- * Set search reference point in WGS84.
- *
- * If set, then only places around this point will be taken into account.
- *
- * @param float $fLat Latitude of point.
- * @param float $fLon Longitude of point.
- * @param float $fRadius Search radius around point.
- *
- * @return void
- */
- public function setNearPoint($fLat, $fLon, $fRadius = 0.1)
- {
- $this->fNearRadius = $fRadius;
- $this->sqlNear = 'ST_SetSRID(ST_Point('.$fLon.','.$fLat.'),4326)';
- }
-
- /**
- * Check if the search is geographically restricted.
- *
- * Searches are restricted if a reference point is given or if
- * a bounded viewbox is set.
- *
- * @return bool True, if the search is geographically bounded.
- */
- public function isBoundedSearch()
- {
- return $this->hasNearPoint() || ($this->sqlViewboxSmall && $this->bViewboxBounded);
- }
-
- /**
- * Set rectangular viewbox.
- *
- * The viewbox may be bounded which means that no search results
- * must be outside the viewbox.
- *
- * @param float[4] $aViewBox Coordinates of the viewbox.
- * @param bool $bBounded True if the viewbox is bounded.
- *
- * @return void
- */
- public function setViewboxFromBox(&$aViewBox, $bBounded)
- {
- $this->bViewboxBounded = $bBounded;
- $this->sqlViewboxCentre = '';
-
- $this->sqlViewboxSmall = sprintf(
- 'ST_SetSRID(ST_MakeBox2D(ST_Point(%F,%F),ST_Point(%F,%F)),4326)',
- $aViewBox[0],
- $aViewBox[1],
- $aViewBox[2],
- $aViewBox[3]
- );
-
- $fHeight = abs($aViewBox[0] - $aViewBox[2]);
- $fWidth = abs($aViewBox[1] - $aViewBox[3]);
-
- $this->sqlViewboxLarge = sprintf(
- 'ST_SetSRID(ST_MakeBox2D(ST_Point(%F,%F),ST_Point(%F,%F)),4326)',
- max($aViewBox[0], $aViewBox[2]) + $fHeight,
- max($aViewBox[1], $aViewBox[3]) + $fWidth,
- min($aViewBox[0], $aViewBox[2]) - $fHeight,
- min($aViewBox[1], $aViewBox[3]) - $fWidth
- );
- }
-
- /**
- * Set viewbox along a route.
- *
- * The viewbox may be bounded which means that no search results
- * must be outside the viewbox.
- *
- * @param object $oDB Nominatim::DB instance to use for computing the box.
- * @param string[] $aRoutePoints List of x,y coordinates along a route.
- * @param float $fRouteWidth Buffer around the route to use.
- * @param bool $bBounded True if the viewbox bounded.
- *
- * @return void
- */
- public function setViewboxFromRoute(&$oDB, $aRoutePoints, $fRouteWidth, $bBounded)
- {
- $this->bViewboxBounded = $bBounded;
- $this->sqlViewboxCentre = "ST_SetSRID('LINESTRING(";
- $sSep = '';
- foreach ($aRoutePoints as $aPoint) {
- $fPoint = (float)$aPoint;
- $this->sqlViewboxCentre .= $sSep.$fPoint;
- $sSep = ($sSep == ' ') ? ',' : ' ';
- }
- $this->sqlViewboxCentre .= ")'::geometry,4326)";
-
- $sSQL = 'ST_BUFFER('.$this->sqlViewboxCentre.','.($fRouteWidth/69).')';
- $sGeom = $oDB->getOne('select '.$sSQL, null, 'Could not get small viewbox');
- $this->sqlViewboxSmall = "'".$sGeom."'::geometry";
-
- $sSQL = 'ST_BUFFER('.$this->sqlViewboxCentre.','.($fRouteWidth/30).')';
- $sGeom = $oDB->getOne('select '.$sSQL, null, 'Could not get large viewbox');
- $this->sqlViewboxLarge = "'".$sGeom."'::geometry";
- }
-
- /**
- * Set list of excluded place IDs.
- *
- * @param integer[] $aExcluded List of IDs.
- *
- * @return void
- */
- public function setExcludeList($aExcluded)
- {
- $this->sqlExcludeList = ' not in ('.join(',', $aExcluded).')';
- }
-
- /**
- * Set list of countries to restrict search to.
- *
- * @param string[] $aCountries List of two-letter lower-case country codes.
- *
- * @return void
- */
- public function setCountryList($aCountries)
- {
- $this->sqlCountryList = '('.join(',', array_map('addQuotes', $aCountries)).')';
- $this->aCountryList = $aCountries;
- }
-
- /**
- * Extract a reference point from a query string.
- *
- * @param string $sQuery Query to scan.
- *
- * @return string The remaining query string.
- */
- public function setNearPointFromQuery($sQuery)
- {
- $aResult = parseLatLon($sQuery);
-
- if ($aResult !== false
- && $aResult[1] <= 90.1
- && $aResult[1] >= -90.1
- && $aResult[2] <= 180.1
- && $aResult[2] >= -180.1
- ) {
- $this->setNearPoint($aResult[1], $aResult[2]);
- $sQuery = trim(str_replace($aResult[0], ' ', $sQuery));
- }
-
- return $sQuery;
- }
-
- /**
- * Get an SQL snippet for computing the distance from the reference point.
- *
- * @param string $sObj SQL variable name to compute the distance from.
- *
- * @return string An SQL string.
- */
- public function distanceSQL($sObj)
- {
- return 'ST_Distance('.$this->sqlNear.", $sObj)";
- }
-
- /**
- * Get an SQL snippet for checking if something is within range of the
- * reference point.
- *
- * @param string $sObj SQL variable name to compute if it is within range.
- *
- * @return string An SQL string.
- */
- public function withinSQL($sObj)
- {
- return sprintf('ST_DWithin(%s, %s, %F)', $sObj, $this->sqlNear, $this->fNearRadius);
- }
-
- /**
- * Get an SQL snippet of the importance factor of the viewbox.
- *
- * The importance factor is computed by checking if an object is within
- * the viewbox and/or the extended version of the viewbox.
- *
- * @param string $sObj SQL variable name of object to weight the importance
- *
- * @return string SQL snippet of the factor with a leading multiply sign.
- */
- public function viewboxImportanceSQL($sObj)
- {
- $sSQL = '';
-
- if ($this->sqlViewboxSmall) {
- $sSQL = " * CASE WHEN ST_Contains($this->sqlViewboxSmall, $sObj) THEN 1 ELSE 0.5 END";
- }
- if ($this->sqlViewboxLarge) {
- $sSQL = " * CASE WHEN ST_Contains($this->sqlViewboxLarge, $sObj) THEN 1 ELSE 0.5 END";
- }
-
- return $sSQL;
- }
-
- /**
- * SQL snippet checking if a place ID should be excluded.
- *
- * @param string $sVariable SQL variable name of place ID to check,
- * potentially prefixed with more SQL.
- *
- * @return string SQL snippet.
- */
- public function excludeSQL($sVariable)
- {
- if ($this->sqlExcludeList) {
- return $sVariable.$this->sqlExcludeList;
- }
-
- return '';
- }
-
- /**
- * Check if the given country is covered by the search context.
- *
- * @param string $sCountryCode Country code of the country to check.
- *
- * @return True, if no country code restrictions are set or the
- * country is included in the country list.
- */
- public function isCountryApplicable($sCountryCode)
- {
- return $this->aCountryList === null || in_array($sCountryCode, $this->aCountryList);
- }
-
- public function debugInfo()
- {
- return array(
- 'Near radius' => $this->fNearRadius,
- 'Near point (SQL)' => $this->sqlNear,
- 'Bounded viewbox' => $this->bViewboxBounded,
- 'Viewbox (SQL, small)' => $this->sqlViewboxSmall,
- 'Viewbox (SQL, large)' => $this->sqlViewboxLarge,
- 'Viewbox (SQL, centre)' => $this->sqlViewboxCentre,
- 'Countries (SQL)' => $this->sqlCountryList,
- 'Excluded IDs (SQL)' => $this->sqlExcludeList
- );
- }
-}
+++ /dev/null
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-namespace Nominatim;
-
-require_once(CONST_LibDir.'/SpecialSearchOperator.php');
-require_once(CONST_LibDir.'/SearchContext.php');
-require_once(CONST_LibDir.'/Result.php');
-
-/**
- * Description of a single interpretation of a search query.
- */
-class SearchDescription
-{
- /// Ranking how well the description fits the query.
- private $iSearchRank = 0;
- /// Country code of country the result must belong to.
- private $sCountryCode = '';
- /// List of word ids making up the name of the object.
- private $aName = array();
- /// True if the name is rare enough to force index use on name.
- private $bRareName = false;
- /// True if the name requires to be accompanied by address terms.
- private $bNameNeedsAddress = false;
- /// List of word ids making up the address of the object.
- private $aAddress = array();
- /// List of word ids that appear in the name but should be ignored.
- private $aNameNonSearch = array();
- /// List of word ids that appear in the address but should be ignored.
- private $aAddressNonSearch = array();
- /// Kind of search for special searches, see Nominatim::Operator.
- private $iOperator = Operator::NONE;
- /// Class of special feature to search for.
- private $sClass = '';
- /// Type of special feature to search for.
- private $sType = '';
- /// Housenumber of the object.
- private $sHouseNumber = '';
- /// Postcode for the object.
- private $sPostcode = '';
- /// Global search constraints.
- private $oContext;
-
- // Temporary values used while creating the search description.
-
- /// Index of phrase currently processed.
- private $iNamePhrase = -1;
-
- /**
- * Create an empty search description.
- *
- * @param object $oContext Global context to use. Will be inherited by
- * all derived search objects.
- */
- public function __construct($oContext)
- {
- $this->oContext = $oContext;
- }
-
- /**
- * Get current search rank.
- *
- * The higher the search rank the lower the likelihood that the
- * search is a correct interpretation of the search query.
- *
- * @return integer Search rank.
- */
- public function getRank()
- {
- return $this->iSearchRank;
- }
-
- /**
- * Extract key/value pairs from a query.
- *
- * Key/value pairs are recognised if they are of the form [<key>=<value>].
- * If multiple terms of this kind are found then all terms are removed
- * but only the first is used for search.
- *
- * @param string $sQuery Original query string.
- *
- * @return string The query string with the special search patterns removed.
- */
- public function extractKeyValuePairs($sQuery)
- {
- // Search for terms of kind [<key>=<value>].
- preg_match_all(
- '/\\[([\\w_]*)=([\\w_]*)\\]/',
- $sQuery,
- $aSpecialTermsRaw,
- PREG_SET_ORDER
- );
-
- foreach ($aSpecialTermsRaw as $aTerm) {
- $sQuery = str_replace($aTerm[0], ' ', $sQuery);
- if (!$this->hasOperator()) {
- $this->setPoiSearch(Operator::TYPE, $aTerm[1], $aTerm[2]);
- }
- }
-
- return $sQuery;
- }
-
- /**
- * Check if the combination of parameters is sensible.
- *
- * @return bool True, if the search looks valid.
- */
- public function isValidSearch()
- {
- if (empty($this->aName)) {
- if ($this->sHouseNumber) {
- return false;
- }
- if (!$this->sClass && !$this->sCountryCode) {
- return false;
- }
- }
- if ($this->bNameNeedsAddress && empty($this->aAddress)) {
- return false;
- }
-
- return true;
- }
-
- /////////// Search building functions
-
- /**
- * Create a copy of this search description adding to search rank.
- *
- * @param integer $iTermCost Cost to add to the current search rank.
- *
- * @return object Cloned search description.
- */
- public function clone($iTermCost)
- {
- $oSearch = clone $this;
- $oSearch->iSearchRank += $iTermCost;
-
- return $oSearch;
- }
-
- /**
- * Check if the search currently includes a name.
- *
- * @param bool bIncludeNonNames If true stop-word tokens are taken into
- * account, too.
- *
- * @return bool True, if search has a name.
- */
- public function hasName($bIncludeNonNames = false)
- {
- return !empty($this->aName)
- || (!empty($this->aNameNonSearch) && $bIncludeNonNames);
- }
-
- /**
- * Check if the search currently includes an address term.
- *
- * @return bool True, if any address term is included, including stop-word
- * terms.
- */
- public function hasAddress()
- {
- return !empty($this->aAddress) || !empty($this->aAddressNonSearch);
- }
-
- /**
- * Check if a country restriction is currently included in the search.
- *
- * @return bool True, if a country restriction is set.
- */
- public function hasCountry()
- {
- return $this->sCountryCode !== '';
- }
-
- /**
- * Check if a postcode is currently included in the search.
- *
- * @return bool True, if a postcode is set.
- */
- public function hasPostcode()
- {
- return $this->sPostcode !== '';
- }
-
- /**
- * Check if a house number is set for the search.
- *
- * @return bool True, if a house number is set.
- */
- public function hasHousenumber()
- {
- return $this->sHouseNumber !== '';
- }
-
- /**
- * Check if a special type of place is requested.
- *
- * param integer iOperator When set, check for the particular
- * operator used for the special type.
- *
- * @return bool True, if speial type is requested or, if requested,
- * a special type with the given operator.
- */
- public function hasOperator($iOperator = null)
- {
- return $iOperator === null ? $this->iOperator != Operator::NONE : $this->iOperator == $iOperator;
- }
-
- /**
- * Add the given token to the list of terms to search for in the address.
- *
- * @param integer iID ID of term to add.
- * @param bool bSearchable Term should be used to search for result
- * (i.e. term is not a stop word).
- */
- public function addAddressToken($iId, $bSearchable = true)
- {
- if ($bSearchable) {
- $this->aAddress[$iId] = $iId;
- } else {
- $this->aAddressNonSearch[$iId] = $iId;
- }
- }
-
- /**
- * Add the given full-word token to the list of terms to search for in the
- * name.
- *
- * @param integer iId ID of term to add.
- * @param bool bRareName True if the term is infrequent enough to not
- * require other constraints for efficient search.
- */
- public function addNameToken($iId, $bRareName)
- {
- $this->aName[$iId] = $iId;
- $this->bRareName = $bRareName;
- $this->bNameNeedsAddress = false;
- }
-
- /**
- * Add the given partial token to the list of terms to search for in
- * the name.
- *
- * @param integer iID ID of term to add.
- * @param bool bSearchable Term should be used to search for result
- * (i.e. term is not a stop word).
- * @param bool bNeedsAddress True if the term is too unspecific to be used
- * in a stand-alone search without an address
- * to narrow down the search.
- * @param integer iPhraseNumber Index of phrase, where the partial term
- * appears.
- */
- public function addPartialNameToken($iId, $bSearchable, $bNeedsAddress, $iPhraseNumber)
- {
- if (empty($this->aName)) {
- $this->bNameNeedsAddress = $bNeedsAddress;
- } elseif ($bSearchable && count($this->aName) >= 2) {
- $this->bNameNeedsAddress = false;
- } else {
- $this->bNameNeedsAddress &= $bNeedsAddress;
- }
- if ($bSearchable) {
- $this->aName[$iId] = $iId;
- } else {
- $this->aNameNonSearch[$iId] = $iId;
- }
- $this->iNamePhrase = $iPhraseNumber;
- }
-
- /**
- * Set country restriction for the search.
- *
- * @param string sCountryCode Country code of country to restrict search to.
- */
- public function setCountry($sCountryCode)
- {
- $this->sCountryCode = $sCountryCode;
- $this->iNamePhrase = -1;
- }
-
- /**
- * Set postcode search constraint.
- *
- * @param string sPostcode Postcode the result should have.
- */
- public function setPostcode($sPostcode)
- {
- $this->sPostcode = $sPostcode;
- $this->iNamePhrase = -1;
- }
-
- /**
- * Make this search a search for a postcode object.
- *
- * @param integer iId Token Id for the postcode.
- * @param string sPostcode Postcode to look for.
- */
- public function setPostcodeAsName($iId, $sPostcode)
- {
- $this->iOperator = Operator::POSTCODE;
- $this->aAddress = array_merge($this->aAddress, $this->aName);
- $this->aName = array($iId => $sPostcode);
- $this->bRareName = true;
- $this->iNamePhrase = -1;
- }
-
- /**
- * Set house number search cnstraint.
- *
- * @param string sNumber House number the result should have.
- */
- public function setHousenumber($sNumber)
- {
- $this->sHouseNumber = $sNumber;
- $this->iNamePhrase = -1;
- }
-
- /**
- * Make this search a search for a house number.
- *
- * @param integer iId Token Id for the house number.
- */
- public function setHousenumberAsName($iId)
- {
- $this->aAddress = array_merge($this->aAddress, $this->aName);
- $this->bRareName = false;
- $this->bNameNeedsAddress = true;
- $this->aName = array($iId => $iId);
- $this->iNamePhrase = -1;
- }
-
- /**
- * Make this search a POI search.
- *
- * In a POI search, objects are not (only) searched by their name
- * but also by the primary OSM key/value pair (class and type in Nominatim).
- *
- * @param integer $iOperator Type of POI search
- * @param string $sClass Class (or OSM tag key) of POI.
- * @param string $sType Type (or OSM tag value) of POI.
- *
- * @return void
- */
- public function setPoiSearch($iOperator, $sClass, $sType)
- {
- $this->iOperator = $iOperator;
- $this->sClass = $sClass;
- $this->sType = $sType;
- $this->iNamePhrase = -1;
- }
-
- public function getNamePhrase()
- {
- return $this->iNamePhrase;
- }
-
- /**
- * Get the global search context.
- *
- * @return object Objects of global search constraints.
- */
- public function getContext()
- {
- return $this->oContext;
- }
-
- /////////// Query functions
-
-
- /**
- * Query database for places that match this search.
- *
- * @param object $oDB Nominatim::DB instance to use.
- * @param integer $iMinRank Minimum address rank to restrict search to.
- * @param integer $iMaxRank Maximum address rank to restrict search to.
- * @param integer $iLimit Maximum number of results.
- *
- * @return mixed[] An array with two fields: IDs contains the list of
- * matching place IDs and houseNumber the houseNumber
- * if applicable or -1 if not.
- */
- public function query(&$oDB, $iMinRank, $iMaxRank, $iLimit)
- {
- $aResults = array();
-
- if ($this->sCountryCode
- && empty($this->aName)
- && !$this->iOperator
- && !$this->sClass
- && !$this->oContext->hasNearPoint()
- ) {
- // Just looking for a country - look it up
- if (4 >= $iMinRank && 4 <= $iMaxRank) {
- $aResults = $this->queryCountry($oDB);
- }
- } elseif (empty($this->aName) && empty($this->aAddress)) {
- // Neither name nor address? Then we must be
- // looking for a POI in a geographic area.
- if ($this->oContext->isBoundedSearch()) {
- $aResults = $this->queryNearbyPoi($oDB, $iLimit);
- }
- } elseif ($this->iOperator == Operator::POSTCODE) {
- // looking for postcode
- $aResults = $this->queryPostcode($oDB, $iLimit);
- } else {
- // Ordinary search:
- // First search for places according to name and address.
- $aResults = $this->queryNamedPlace(
- $oDB,
- $iMinRank,
- $iMaxRank,
- $iLimit
- );
-
- // finally get POIs if requested
- if ($this->sClass && !empty($aResults)) {
- $aResults = $this->queryPoiByOperator($oDB, $aResults, $iLimit);
- }
- }
-
- Debug::printDebugTable('Place IDs', $aResults);
-
- if (!empty($aResults) && $this->sPostcode) {
- $sPlaceIds = Result::joinIdsByTable($aResults, Result::TABLE_PLACEX);
- if ($sPlaceIds) {
- $sSQL = 'SELECT place_id FROM placex';
- $sSQL .= ' WHERE place_id in ('.$sPlaceIds.')';
- $sSQL .= " AND postcode != '".$this->sPostcode."'";
- Debug::printSQL($sSQL);
- $aFilteredPlaceIDs = $oDB->getCol($sSQL);
- if ($aFilteredPlaceIDs) {
- foreach ($aFilteredPlaceIDs as $iPlaceId) {
- $aResults[$iPlaceId]->iResultRank++;
- }
- }
- }
- }
-
- return $aResults;
- }
-
-
- private function queryCountry(&$oDB)
- {
- $sSQL = 'SELECT place_id FROM placex ';
- $sSQL .= "WHERE country_code='".$this->sCountryCode."'";
- $sSQL .= ' AND rank_search = 4';
- if ($this->oContext->bViewboxBounded) {
- $sSQL .= ' AND ST_Intersects('.$this->oContext->sqlViewboxSmall.', geometry)';
- }
- $sSQL .= ' ORDER BY st_area(geometry) DESC LIMIT 1';
-
- Debug::printSQL($sSQL);
-
- $iPlaceId = $oDB->getOne($sSQL);
-
- $aResults = array();
- if ($iPlaceId) {
- $aResults[$iPlaceId] = new Result($iPlaceId);
- }
-
- return $aResults;
- }
-
- private function queryNearbyPoi(&$oDB, $iLimit)
- {
- if (!$this->sClass) {
- return array();
- }
-
- $aDBResults = array();
- $sPoiTable = $this->poiTable();
-
- if ($oDB->tableExists($sPoiTable)) {
- $sSQL = 'SELECT place_id FROM '.$sPoiTable.' ct';
- if ($this->oContext->sqlCountryList) {
- $sSQL .= ' JOIN placex USING (place_id)';
- }
- if ($this->oContext->hasNearPoint()) {
- $sSQL .= ' WHERE '.$this->oContext->withinSQL('ct.centroid');
- } elseif ($this->oContext->bViewboxBounded) {
- $sSQL .= ' WHERE ST_Contains('.$this->oContext->sqlViewboxSmall.', ct.centroid)';
- }
- if ($this->oContext->sqlCountryList) {
- $sSQL .= ' AND country_code in '.$this->oContext->sqlCountryList;
- }
- $sSQL .= $this->oContext->excludeSQL(' AND place_id');
- if ($this->oContext->sqlViewboxCentre) {
- $sSQL .= ' ORDER BY ST_Distance(';
- $sSQL .= $this->oContext->sqlViewboxCentre.', ct.centroid) ASC';
- } elseif ($this->oContext->hasNearPoint()) {
- $sSQL .= ' ORDER BY '.$this->oContext->distanceSQL('ct.centroid').' ASC';
- }
- $sSQL .= " LIMIT $iLimit";
- Debug::printSQL($sSQL);
- $aDBResults = $oDB->getCol($sSQL);
- }
-
- if ($this->oContext->hasNearPoint()) {
- $sSQL = 'SELECT place_id FROM placex WHERE ';
- $sSQL .= 'class = :class and type = :type';
- $sSQL .= ' AND '.$this->oContext->withinSQL('geometry');
- $sSQL .= ' AND linked_place_id is null';
- if ($this->oContext->sqlCountryList) {
- $sSQL .= ' AND country_code in '.$this->oContext->sqlCountryList;
- }
- $sSQL .= ' ORDER BY '.$this->oContext->distanceSQL('centroid').' ASC';
- $sSQL .= " LIMIT $iLimit";
- Debug::printSQL($sSQL);
- $aDBResults = $oDB->getCol(
- $sSQL,
- array(':class' => $this->sClass, ':type' => $this->sType)
- );
- }
-
- $aResults = array();
- foreach ($aDBResults as $iPlaceId) {
- $aResults[$iPlaceId] = new Result($iPlaceId);
- }
-
- return $aResults;
- }
-
- private function queryPostcode(&$oDB, $iLimit)
- {
- $sSQL = 'SELECT p.place_id FROM location_postcode p ';
-
- if (!empty($this->aAddress)) {
- $sSQL .= ', search_name s ';
- $sSQL .= 'WHERE s.place_id = p.parent_place_id ';
- $sSQL .= 'AND array_cat(s.nameaddress_vector, s.name_vector)';
- $sSQL .= ' @> '.$oDB->getArraySQL($this->aAddress).' AND ';
- } else {
- $sSQL .= 'WHERE ';
- }
-
- $sSQL .= "p.postcode = '".reset($this->aName)."'";
- $sSQL .= $this->countryCodeSQL(' AND p.country_code');
- if ($this->oContext->bViewboxBounded) {
- $sSQL .= ' AND ST_Intersects('.$this->oContext->sqlViewboxSmall.', geometry)';
- }
- $sSQL .= $this->oContext->excludeSQL(' AND p.place_id');
- $sSQL .= " LIMIT $iLimit";
-
- Debug::printSQL($sSQL);
-
- $aResults = array();
- foreach ($oDB->getCol($sSQL) as $iPlaceId) {
- $aResults[$iPlaceId] = new Result($iPlaceId, Result::TABLE_POSTCODE);
- }
-
- return $aResults;
- }
-
- private function queryNamedPlace(&$oDB, $iMinAddressRank, $iMaxAddressRank, $iLimit)
- {
- $aTerms = array();
- $aOrder = array();
-
- if (!empty($this->aName)) {
- $aTerms[] = 'name_vector @> '.$oDB->getArraySQL($this->aName);
- }
- if (!empty($this->aAddress)) {
- // For infrequent name terms disable index usage for address
- if ($this->bRareName) {
- $aTerms[] = 'array_cat(nameaddress_vector,ARRAY[]::integer[]) @> '.$oDB->getArraySQL($this->aAddress);
- } else {
- $aTerms[] = 'nameaddress_vector @> '.$oDB->getArraySQL($this->aAddress);
- }
- }
-
- $sCountryTerm = $this->countryCodeSQL('country_code');
- if ($sCountryTerm) {
- $aTerms[] = $sCountryTerm;
- }
-
- if ($this->sHouseNumber) {
- $aTerms[] = 'address_rank between 16 and 30';
- } elseif (!$this->sClass || $this->iOperator == Operator::NAME) {
- if ($iMinAddressRank > 0) {
- $aTerms[] = "((address_rank between $iMinAddressRank and $iMaxAddressRank) or (search_rank between $iMinAddressRank and $iMaxAddressRank))";
- }
- }
-
- if ($this->oContext->hasNearPoint()) {
- $aTerms[] = $this->oContext->withinSQL('centroid');
- $aOrder[] = $this->oContext->distanceSQL('centroid');
- } elseif ($this->sPostcode) {
- if (empty($this->aAddress)) {
- $aTerms[] = "EXISTS(SELECT place_id FROM location_postcode p WHERE p.postcode = '".$this->sPostcode."' AND ST_DWithin(search_name.centroid, p.geometry, 0.12))";
- } else {
- $aOrder[] = "(SELECT min(ST_Distance(search_name.centroid, p.geometry)) FROM location_postcode p WHERE p.postcode = '".$this->sPostcode."')";
- }
- }
-
- $sExcludeSQL = $this->oContext->excludeSQL('place_id');
- if ($sExcludeSQL) {
- $aTerms[] = $sExcludeSQL;
- }
-
- if ($this->oContext->bViewboxBounded) {
- $aTerms[] = 'centroid && '.$this->oContext->sqlViewboxSmall;
- }
-
- if ($this->sHouseNumber) {
- $sImportanceSQL = '- abs(26 - address_rank) + 3';
- } else {
- $sImportanceSQL = '(CASE WHEN importance = 0 OR importance IS NULL THEN 0.75001-(search_rank::float/40) ELSE importance END)';
- }
- $sImportanceSQL .= $this->oContext->viewboxImportanceSQL('centroid');
- $aOrder[] = "$sImportanceSQL DESC";
-
- $aFullNameAddress = $this->oContext->getFullNameTerms();
- if (!empty($aFullNameAddress)) {
- $sExactMatchSQL = ' ( ';
- $sExactMatchSQL .= ' SELECT count(*) FROM ( ';
- $sExactMatchSQL .= ' SELECT unnest('.$oDB->getArraySQL($aFullNameAddress).')';
- $sExactMatchSQL .= ' INTERSECT ';
- $sExactMatchSQL .= ' SELECT unnest(nameaddress_vector)';
- $sExactMatchSQL .= ' ) s';
- $sExactMatchSQL .= ') as exactmatch';
- $aOrder[] = 'exactmatch DESC';
- } else {
- $sExactMatchSQL = '0::int as exactmatch';
- }
-
- if (empty($aTerms)) {
- return array();
- }
-
- if ($this->hasHousenumber()) {
- $sHouseNumberRegex = $oDB->getDBQuoted('\\\\m'.$this->sHouseNumber.'\\\\M');
-
- // Housenumbers on streets and places.
- $sPlacexSql = 'SELECT array_agg(place_id) FROM placex';
- $sPlacexSql .= ' WHERE parent_place_id = sin.place_id AND sin.address_rank < 30';
- $sPlacexSql .= $this->oContext->excludeSQL(' AND place_id');
- $sPlacexSql .= ' and housenumber ~* E'.$sHouseNumberRegex;
-
- // Interpolations on streets and places.
- $sInterpolSql = 'null';
- $sTigerSql = 'null';
- if (preg_match('/^[0-9]+$/', $this->sHouseNumber)) {
- $sIpolHnr = 'WHERE parent_place_id = sin.place_id ';
- $sIpolHnr .= ' AND startnumber is not NULL AND sin.address_rank < 30';
- $sIpolHnr .= ' AND '.$this->sHouseNumber.' between startnumber and endnumber';
- $sIpolHnr .= ' AND ('.$this->sHouseNumber.' - startnumber) % step = 0';
-
- $sInterpolSql = 'SELECT array_agg(place_id) FROM location_property_osmline '.$sIpolHnr;
- if (CONST_Use_US_Tiger_Data) {
- $sTigerSql = 'SELECT array_agg(place_id) FROM location_property_tiger '.$sIpolHnr;
- $sTigerSql .= " and sin.country_code = 'us'";
- }
- }
-
- if ($this->sClass) {
- $iLimit = 40;
- }
-
- $sSelfHnr = 'SELECT * FROM placex WHERE place_id = search_name.place_id';
- $sSelfHnr .= ' AND housenumber ~* E'.$sHouseNumberRegex;
-
- $aTerms[] = '(address_rank < 30 or exists('.$sSelfHnr.'))';
-
-
- $sSQL = 'SELECT sin.*, ';
- $sSQL .= '('.$sPlacexSql.') as placex_hnr, ';
- $sSQL .= '('.$sInterpolSql.') as interpol_hnr, ';
- $sSQL .= '('.$sTigerSql.') as tiger_hnr ';
- $sSQL .= ' FROM (';
- $sSQL .= ' SELECT place_id, address_rank, country_code,'.$sExactMatchSQL.',';
- $sSQL .= ' CASE WHEN importance = 0 OR importance IS NULL';
- $sSQL .= ' THEN 0.75001-(search_rank::float/40) ELSE importance END as importance';
- $sSQL .= ' FROM search_name';
- $sSQL .= ' WHERE '.join(' and ', $aTerms);
- $sSQL .= ' ORDER BY '.join(', ', $aOrder);
- $sSQL .= ' LIMIT 40000';
- $sSQL .= ') as sin';
- $sSQL .= ' ORDER BY address_rank = 30 desc, placex_hnr, interpol_hnr, tiger_hnr,';
- $sSQL .= ' importance';
- $sSQL .= ' LIMIT '.$iLimit;
- } else {
- if ($this->sClass) {
- $iLimit = 40;
- }
-
- $sSQL = 'SELECT place_id, address_rank, '.$sExactMatchSQL;
- $sSQL .= ' FROM search_name';
- $sSQL .= ' WHERE '.join(' and ', $aTerms);
- $sSQL .= ' ORDER BY '.join(', ', $aOrder);
- $sSQL .= ' LIMIT '.$iLimit;
- }
-
- Debug::printSQL($sSQL);
-
- $aDBResults = $oDB->getAll($sSQL, null, 'Could not get places for search terms.');
-
- $aResults = array();
-
- foreach ($aDBResults as $aResult) {
- $oResult = new Result($aResult['place_id']);
- $oResult->iExactMatches = $aResult['exactmatch'];
- $oResult->iAddressRank = $aResult['address_rank'];
-
- $bNeedResult = true;
- if ($this->hasHousenumber() && $aResult['address_rank'] < 30) {
- if ($aResult['placex_hnr']) {
- foreach (explode(',', substr($aResult['placex_hnr'], 1, -1)) as $sPlaceID) {
- $iPlaceID = intval($sPlaceID);
- $oHnrResult = new Result($iPlaceID);
- $oHnrResult->iExactMatches = $aResult['exactmatch'];
- $oHnrResult->iAddressRank = 30;
- $aResults[$iPlaceID] = $oHnrResult;
- $bNeedResult = false;
- }
- }
- if ($aResult['interpol_hnr']) {
- foreach (explode(',', substr($aResult['interpol_hnr'], 1, -1)) as $sPlaceID) {
- $iPlaceID = intval($sPlaceID);
- $oHnrResult = new Result($iPlaceID, Result::TABLE_OSMLINE);
- $oHnrResult->iExactMatches = $aResult['exactmatch'];
- $oHnrResult->iAddressRank = 30;
- $oHnrResult->iHouseNumber = intval($this->sHouseNumber);
- $aResults[$iPlaceID] = $oHnrResult;
- $bNeedResult = false;
- }
- }
- if ($aResult['tiger_hnr']) {
- foreach (explode(',', substr($aResult['tiger_hnr'], 1, -1)) as $sPlaceID) {
- $iPlaceID = intval($sPlaceID);
- $oHnrResult = new Result($iPlaceID, Result::TABLE_TIGER);
- $oHnrResult->iExactMatches = $aResult['exactmatch'];
- $oHnrResult->iAddressRank = 30;
- $oHnrResult->iHouseNumber = intval($this->sHouseNumber);
- $aResults[$iPlaceID] = $oHnrResult;
- $bNeedResult = false;
- }
- }
-
- if ($aResult['address_rank'] < 26) {
- $oResult->iResultRank += 2;
- } else {
- $oResult->iResultRank++;
- }
- }
-
- if ($bNeedResult) {
- $aResults[$aResult['place_id']] = $oResult;
- }
- }
-
- return $aResults;
- }
-
-
- private function queryPoiByOperator(&$oDB, $aParentIDs, $iLimit)
- {
- $aResults = array();
- $sPlaceIDs = Result::joinIdsByTable($aParentIDs, Result::TABLE_PLACEX);
-
- if (!$sPlaceIDs) {
- return $aResults;
- }
-
- if ($this->iOperator == Operator::TYPE || $this->iOperator == Operator::NAME) {
- // If they were searching for a named class (i.e. 'Kings Head pub')
- // then we might have an extra match
- $sSQL = 'SELECT place_id FROM placex ';
- $sSQL .= " WHERE place_id in ($sPlaceIDs)";
- $sSQL .= " AND class='".$this->sClass."' ";
- $sSQL .= " AND type='".$this->sType."'";
- $sSQL .= ' AND linked_place_id is null';
- $sSQL .= $this->oContext->excludeSQL(' AND place_id');
- $sSQL .= ' ORDER BY rank_search ASC ';
- $sSQL .= " LIMIT $iLimit";
-
- Debug::printSQL($sSQL);
-
- foreach ($oDB->getCol($sSQL) as $iPlaceId) {
- $aResults[$iPlaceId] = new Result($iPlaceId);
- }
- }
-
- // NEAR and IN are handled the same
- if ($this->iOperator == Operator::TYPE || $this->iOperator == Operator::NEAR) {
- $sClassTable = $this->poiTable();
- $bCacheTable = $oDB->tableExists($sClassTable);
-
- $sSQL = "SELECT min(rank_search) FROM placex WHERE place_id in ($sPlaceIDs)";
- Debug::printSQL($sSQL);
- $iMaxRank = (int) $oDB->getOne($sSQL);
-
- // For state / country level searches the normal radius search doesn't work very well
- $sPlaceGeom = false;
- if ($iMaxRank < 9 && $bCacheTable) {
- // Try and get a polygon to search in instead
- $sSQL = 'SELECT geometry FROM placex';
- $sSQL .= " WHERE place_id in ($sPlaceIDs)";
- $sSQL .= " AND rank_search < $iMaxRank + 5";
- $sSQL .= ' AND ST_Area(Box2d(geometry)) < 20';
- $sSQL .= " AND ST_GeometryType(geometry) in ('ST_Polygon','ST_MultiPolygon')";
- $sSQL .= ' ORDER BY rank_search ASC ';
- $sSQL .= ' LIMIT 1';
- Debug::printSQL($sSQL);
- $sPlaceGeom = $oDB->getOne($sSQL);
- }
-
- if ($sPlaceGeom) {
- $sPlaceIDs = false;
- } else {
- $iMaxRank += 5;
- $sSQL = 'SELECT place_id FROM placex';
- $sSQL .= " WHERE place_id in ($sPlaceIDs) and rank_search < $iMaxRank";
- Debug::printSQL($sSQL);
- $aPlaceIDs = $oDB->getCol($sSQL);
- $sPlaceIDs = join(',', $aPlaceIDs);
- }
-
- if ($sPlaceIDs || $sPlaceGeom) {
- $fRange = 0.01;
- if ($bCacheTable) {
- // More efficient - can make the range bigger
- $fRange = 0.05;
-
- $sOrderBySQL = '';
- if ($this->oContext->hasNearPoint()) {
- $sOrderBySQL = $this->oContext->distanceSQL('l.centroid');
- } elseif ($sPlaceIDs) {
- $sOrderBySQL = 'ST_Distance(l.centroid, f.geometry)';
- } elseif ($sPlaceGeom) {
- $sOrderBySQL = "ST_Distance(st_centroid('".$sPlaceGeom."'), l.centroid)";
- }
-
- $sSQL = 'SELECT distinct i.place_id';
- if ($sOrderBySQL) {
- $sSQL .= ', i.order_term';
- }
- $sSQL .= ' from (SELECT l.place_id';
- if ($sOrderBySQL) {
- $sSQL .= ','.$sOrderBySQL.' as order_term';
- }
- $sSQL .= ' from '.$sClassTable.' as l';
-
- if ($sPlaceIDs) {
- $sSQL .= ',placex as f WHERE ';
- $sSQL .= "f.place_id in ($sPlaceIDs) ";
- $sSQL .= " AND ST_DWithin(l.centroid, f.centroid, $fRange)";
- } elseif ($sPlaceGeom) {
- $sSQL .= " WHERE ST_Contains('$sPlaceGeom', l.centroid)";
- }
-
- $sSQL .= $this->oContext->excludeSQL(' AND l.place_id');
- $sSQL .= 'limit 300) i ';
- if ($sOrderBySQL) {
- $sSQL .= 'order by order_term asc';
- }
- $sSQL .= " limit $iLimit";
-
- Debug::printSQL($sSQL);
-
- foreach ($oDB->getCol($sSQL) as $iPlaceId) {
- $aResults[$iPlaceId] = new Result($iPlaceId);
- }
- } else {
- if ($this->oContext->hasNearPoint()) {
- $fRange = $this->oContext->nearRadius();
- }
-
- $sOrderBySQL = '';
- if ($this->oContext->hasNearPoint()) {
- $sOrderBySQL = $this->oContext->distanceSQL('l.geometry');
- } else {
- $sOrderBySQL = 'ST_Distance(l.geometry, f.geometry)';
- }
-
- $sSQL = 'SELECT distinct l.place_id';
- if ($sOrderBySQL) {
- $sSQL .= ','.$sOrderBySQL.' as orderterm';
- }
- $sSQL .= ' FROM placex as l, placex as f';
- $sSQL .= " WHERE f.place_id in ($sPlaceIDs)";
- $sSQL .= " AND ST_DWithin(l.geometry, f.centroid, $fRange)";
- $sSQL .= " AND l.class='".$this->sClass."'";
- $sSQL .= " AND l.type='".$this->sType."'";
- $sSQL .= $this->oContext->excludeSQL(' AND l.place_id');
- if ($sOrderBySQL) {
- $sSQL .= 'ORDER BY orderterm ASC';
- }
- $sSQL .= " limit $iLimit";
-
- Debug::printSQL($sSQL);
-
- foreach ($oDB->getCol($sSQL) as $iPlaceId) {
- $aResults[$iPlaceId] = new Result($iPlaceId);
- }
- }
- }
- }
-
- return $aResults;
- }
-
- private function poiTable()
- {
- return 'place_classtype_'.$this->sClass.'_'.$this->sType;
- }
-
- private function countryCodeSQL($sVar)
- {
- if ($this->sCountryCode) {
- return $sVar.' = \''.$this->sCountryCode."'";
- }
- if ($this->oContext->sqlCountryList) {
- return $sVar.' in '.$this->oContext->sqlCountryList;
- }
-
- return '';
- }
-
- /////////// Sort functions
-
-
- public static function bySearchRank($a, $b)
- {
- if ($a->iSearchRank == $b->iSearchRank) {
- return $a->iOperator + strlen($a->sHouseNumber)
- - $b->iOperator - strlen($b->sHouseNumber);
- }
-
- return $a->iSearchRank < $b->iSearchRank ? -1 : 1;
- }
-
- //////////// Debugging functions
-
-
- public function debugInfo()
- {
- return array(
- 'Search rank' => $this->iSearchRank,
- 'Country code' => $this->sCountryCode,
- 'Name terms' => $this->aName,
- 'Name terms (stop words)' => $this->aNameNonSearch,
- 'Address terms' => $this->aAddress,
- 'Address terms (stop words)' => $this->aAddressNonSearch,
- 'Address terms (full words)' => $this->aFullNameAddress ?? '',
- 'Special search' => $this->iOperator,
- 'Class' => $this->sClass,
- 'Type' => $this->sType,
- 'House number' => $this->sHouseNumber,
- 'Postcode' => $this->sPostcode
- );
- }
-
- public function dumpAsHtmlTableRow(&$aWordIDs)
- {
- $kf = function ($k) use (&$aWordIDs) {
- return $aWordIDs[$k] ?? '['.$k.']';
- };
-
- echo '<tr>';
- echo "<td>$this->iSearchRank</td>";
- echo '<td>'.join(', ', array_map($kf, $this->aName)).'</td>';
- echo '<td>'.join(', ', array_map($kf, $this->aNameNonSearch)).'</td>';
- echo '<td>'.join(', ', array_map($kf, $this->aAddress)).'</td>';
- echo '<td>'.join(', ', array_map($kf, $this->aAddressNonSearch)).'</td>';
- echo '<td>'.$this->sCountryCode.'</td>';
- echo '<td>'.Operator::toString($this->iOperator).'</td>';
- echo '<td>'.$this->sClass.'</td>';
- echo '<td>'.$this->sType.'</td>';
- echo '<td>'.$this->sPostcode.'</td>';
- echo '<td>'.$this->sHouseNumber.'</td>';
-
- echo '</tr>';
- }
-}
+++ /dev/null
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-namespace Nominatim;
-
-/**
- * Description of the position of a token within a query.
- */
-class SearchPosition
-{
- private $sPhraseType;
-
- private $iPhrase;
- private $iNumPhrases;
-
- private $iToken;
- private $iNumTokens;
-
-
- public function __construct($sPhraseType, $iPhrase, $iNumPhrases)
- {
- $this->sPhraseType = $sPhraseType;
- $this->iPhrase = $iPhrase;
- $this->iNumPhrases = $iNumPhrases;
- }
-
- public function setTokenPosition($iToken, $iNumTokens)
- {
- $this->iToken = $iToken;
- $this->iNumTokens = $iNumTokens;
- }
-
- /**
- * Check if the phrase can be of the given type.
- *
- * @param string $sType Type of phrse requested.
- *
- * @return True if the phrase is untyped or of the given type.
- */
- public function maybePhrase($sType)
- {
- return $this->sPhraseType == '' || $this->sPhraseType == $sType;
- }
-
- /**
- * Check if the phrase is exactly of the given type.
- *
- * @param string $sType Type of phrse requested.
- *
- * @return True if the phrase of the given type.
- */
- public function isPhrase($sType)
- {
- return $this->sPhraseType == $sType;
- }
-
- /**
- * Return true if the token is the very first in the query.
- */
- public function isFirstToken()
- {
- return $this->iPhrase == 0 && $this->iToken == 0;
- }
-
- /**
- * Check if the token is the final one in the query.
- */
- public function isLastToken()
- {
- return $this->iToken + 1 == $this->iNumTokens && $this->iPhrase + 1 == $this->iNumPhrases;
- }
-
- /**
- * Check if the current token is part of the first phrase in the query.
- */
- public function isFirstPhrase()
- {
- return $this->iPhrase == 0;
- }
-
- /**
- * Get the phrase position in the query.
- */
- public function getPhrase()
- {
- return $this->iPhrase;
- }
-}
+++ /dev/null
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-namespace Nominatim;
-
-class Shell
-{
- public function __construct($sBaseCmd, ...$aParams)
- {
- if (!$sBaseCmd) {
- throw new \Exception('Command missing in new() call');
- }
- $this->baseCmd = $sBaseCmd;
- $this->aParams = array();
- $this->aEnv = null; // null = use the same environment as the current PHP process
-
- $this->stdoutString = null;
-
- foreach ($aParams as $sParam) {
- $this->addParams($sParam);
- }
- }
-
- public function addParams(...$aParams)
- {
- foreach ($aParams as $sParam) {
- if (isset($sParam) && $sParam !== null && $sParam !== '') {
- array_push($this->aParams, $sParam);
- }
- }
- return $this;
- }
-
- public function addEnvPair($sKey, $sVal)
- {
- if (isset($sKey) && $sKey && isset($sVal)) {
- if (!isset($this->aEnv)) {
- $this->aEnv = $_ENV;
- }
- $this->aEnv = array_merge($this->aEnv, array($sKey => $sVal), $_ENV);
- }
- return $this;
- }
-
- public function escapedCmd()
- {
- $aEscaped = array_map(function ($sParam) {
- return $this->escapeParam($sParam);
- }, array_merge(array($this->baseCmd), $this->aParams));
-
- return join(' ', $aEscaped);
- }
-
- public function run($bExitOnFail = false)
- {
- $sCmd = $this->escapedCmd();
- // $aEnv does not need escaping, proc_open seems to handle it fine
-
- $aFDs = array(
- 0 => array('pipe', 'r'),
- 1 => STDOUT,
- 2 => STDERR
- );
- $aPipes = null;
- $hProc = @proc_open($sCmd, $aFDs, $aPipes, null, $this->aEnv);
- if (!is_resource($hProc)) {
- throw new \Exception('Unable to run command: ' . $sCmd);
- }
-
- fclose($aPipes[0]); // no stdin
-
- $iStat = proc_close($hProc);
-
- if ($iStat != 0 && $bExitOnFail) {
- exit($iStat);
- }
-
- return $iStat;
- }
-
- private function escapeParam($sParam)
- {
- return (preg_match('/^-*\w+$/', $sParam)) ? $sParam : escapeshellarg($sParam);
- }
-}
+++ /dev/null
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-namespace Nominatim;
-
-/**
- * A word list creator based on simple splitting by space.
- *
- * Creates possible permutations of split phrases by finding all combination
- * of splitting the phrase on space boundaries.
- */
-class SimpleWordList
-{
- const MAX_WORDSET_LEN = 20;
- const MAX_WORDSETS = 100;
-
- // The phrase as a list of simple terms (without spaces).
- private $aWords;
-
- /**
- * Create a new word list
- *
- * @param string sPhrase Phrase to create the word list from. The phrase is
- * expected to be normalised, so that there are no
- * subsequent spaces.
- */
- public function __construct($sPhrase)
- {
- if (strlen($sPhrase) > 0) {
- $this->aWords = explode(' ', $sPhrase);
- } else {
- $this->aWords = array();
- }
- }
-
- /**
- * Get all possible tokens that are present in this word list.
- *
- * @return array The list of string tokens in the word list.
- */
- public function getTokens()
- {
- $aTokens = array();
- $iNumWords = count($this->aWords);
-
- for ($i = 0; $i < $iNumWords; $i++) {
- $sPhrase = $this->aWords[$i];
- $aTokens[$sPhrase] = $sPhrase;
-
- for ($j = $i + 1; $j < $iNumWords; $j++) {
- $sPhrase .= ' '.$this->aWords[$j];
- $aTokens[$sPhrase] = $sPhrase;
- }
- }
-
- return $aTokens;
- }
-
- /**
- * Compute all possible permutations of phrase splits that result in
- * words which are in the token list.
- */
- public function getWordSets($oTokens)
- {
- $iNumWords = count($this->aWords);
-
- if ($iNumWords == 0) {
- return null;
- }
-
- // Caches the word set for the partial phrase up to word i.
- $aSetCache = array_fill(0, $iNumWords, array());
-
- // Initialise first element of cache. There can only be the word.
- if ($oTokens->containsAny($this->aWords[0])) {
- $aSetCache[0][] = array($this->aWords[0]);
- }
-
- // Now do the next elements using what we already have.
- for ($i = 1; $i < $iNumWords; $i++) {
- for ($j = $i; $j > 0; $j--) {
- $sPartial = $j == $i ? $this->aWords[$j] : $this->aWords[$j].' '.$sPartial;
- if (!empty($aSetCache[$j - 1]) && $oTokens->containsAny($sPartial)) {
- $aPartial = array($sPartial);
- foreach ($aSetCache[$j - 1] as $aSet) {
- if (count($aSet) < SimpleWordList::MAX_WORDSET_LEN) {
- $aSetCache[$i][] = array_merge($aSet, $aPartial);
- }
- }
- if (count($aSetCache[$i]) > 2 * SimpleWordList::MAX_WORDSETS) {
- usort(
- $aSetCache[$i],
- array('\Nominatim\SimpleWordList', 'cmpByArraylen')
- );
- $aSetCache[$i] = array_slice(
- $aSetCache[$i],
- 0,
- SimpleWordList::MAX_WORDSETS
- );
- }
- }
- }
-
- // finally the current full phrase
- $sPartial = $this->aWords[0].' '.$sPartial;
- if ($oTokens->containsAny($sPartial)) {
- $aSetCache[$i][] = array($sPartial);
- }
- }
-
- $aWordSets = $aSetCache[$iNumWords - 1];
- usort($aWordSets, array('\Nominatim\SimpleWordList', 'cmpByArraylen'));
- return array_slice($aWordSets, 0, SimpleWordList::MAX_WORDSETS);
- }
-
- /**
- * Custom search routine which takes two arrays. The array with the fewest
- * items wins. If same number of items then the one with the longest first
- * element wins.
- */
- public static function cmpByArraylen($aA, $aB)
- {
- $iALen = count($aA);
- $iBLen = count($aB);
-
- if ($iALen == $iBLen) {
- return strlen($aB[0]) <=> strlen($aA[0]);
- }
-
- return ($iALen < $iBLen) ? -1 : 1;
- }
-
- public function debugInfo()
- {
- return $this->aWords;
- }
-}
+++ /dev/null
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-namespace Nominatim;
-
-/**
- * Operators describing special searches.
- */
-abstract class Operator
-{
- /// No operator selected.
- const NONE = 0;
- /// Search for POI of the given type.
- const TYPE = 1;
- /// Search for POIs near the given place.
- const NEAR = 2;
- /// Search for POIS in the given place.
- const IN = 3;
- /// Search for POIS named as given.
- const NAME = 4;
- /// Search for postcodes.
- const POSTCODE = 5;
-
- private static $aConstantNames = null;
-
-
- public static function toString($iOperator)
- {
- if ($iOperator == Operator::NONE) {
- return '';
- }
-
- if (Operator::$aConstantNames === null) {
- $oReflector = new \ReflectionClass('Nominatim\Operator');
- $aConstants = $oReflector->getConstants();
-
- Operator::$aConstantNames = array();
- foreach ($aConstants as $sName => $iValue) {
- Operator::$aConstantNames[$iValue] = $sName;
- }
- }
-
- return Operator::$aConstantNames[$iOperator];
- }
-}
+++ /dev/null
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-namespace Nominatim;
-
-require_once(CONST_TokenizerDir.'/tokenizer.php');
-
-use Exception;
-
-class Status
-{
- protected $oDB;
-
- public function __construct(&$oDB)
- {
- $this->oDB =& $oDB;
- }
-
- public function status()
- {
- if (!$this->oDB) {
- throw new Exception('No database', 700);
- }
-
- try {
- $this->oDB->connect();
- } catch (\Nominatim\DatabaseError $e) {
- throw new Exception('Database connection failed', 700);
- }
-
- $oTokenizer = new \Nominatim\Tokenizer($this->oDB);
- $oTokenizer->checkStatus();
- }
-
- public function dataDate()
- {
- $sSQL = 'SELECT EXTRACT(EPOCH FROM lastimportdate) FROM import_status LIMIT 1';
- $iDataDateEpoch = $this->oDB->getOne($sSQL);
-
- if ($iDataDateEpoch === false) {
- throw new Exception('Import date is not available', 705);
- }
-
- return $iDataDateEpoch;
- }
-
- public function databaseVersion()
- {
- $sSQL = 'SELECT value FROM nominatim_properties WHERE property = \'database_version\'';
- return $this->oDB->getOne($sSQL);
- }
-}
+++ /dev/null
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-namespace Nominatim\Token;
-
-/**
- * A country token.
- */
-class Country
-{
- /// Database word id, if available.
- private $iId;
- /// Two-letter country code (lower-cased).
- private $sCountryCode;
-
- public function __construct($iId, $sCountryCode)
- {
- $this->iId = $iId;
- $this->sCountryCode = $sCountryCode;
- }
-
- public function getId()
- {
- return $this->iId;
- }
-
- /**
- * Check if the token can be added to the given search.
- * Derive new searches by adding this token to an existing search.
- *
- * @param object $oSearch Partial search description derived so far.
- * @param object $oPosition Description of the token position within
- the query.
- *
- * @return True if the token is compatible with the search configuration
- * given the position.
- */
- public function isExtendable($oSearch, $oPosition)
- {
- return !$oSearch->hasCountry()
- && $oPosition->maybePhrase('country')
- && $oSearch->getContext()->isCountryApplicable($this->sCountryCode);
- }
-
- /**
- * Derive new searches by adding this token to an existing search.
- *
- * @param object $oSearch Partial search description derived so far.
- * @param object $oPosition Description of the token position within
- the query.
- *
- * @return SearchDescription[] List of derived search descriptions.
- */
- public function extendSearch($oSearch, $oPosition)
- {
- $oNewSearch = $oSearch->clone($oPosition->isLastToken() ? 1 : 6);
- $oNewSearch->setCountry($this->sCountryCode);
-
- return array($oNewSearch);
- }
-
- public function debugInfo()
- {
- return array(
- 'ID' => $this->iId,
- 'Type' => 'country',
- 'Info' => $this->sCountryCode
- );
- }
-
- public function debugCode()
- {
- return 'C';
- }
-}
+++ /dev/null
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-namespace Nominatim\Token;
-
-/**
- * A house number token.
- */
-class HouseNumber
-{
- /// Database word id, if available.
- private $iId;
- /// Normalized house number.
- private $sToken;
-
- public function __construct($iId, $sToken)
- {
- $this->iId = $iId;
- $this->sToken = $sToken;
- }
-
- public function getId()
- {
- return $this->iId;
- }
-
- /**
- * Check if the token can be added to the given search.
- * Derive new searches by adding this token to an existing search.
- *
- * @param object $oSearch Partial search description derived so far.
- * @param object $oPosition Description of the token position within
- the query.
- *
- * @return True if the token is compatible with the search configuration
- * given the position.
- */
- public function isExtendable($oSearch, $oPosition)
- {
- return !$oSearch->hasHousenumber()
- && !$oSearch->hasOperator(\Nominatim\Operator::POSTCODE)
- && $oPosition->maybePhrase('street');
- }
-
- /**
- * Derive new searches by adding this token to an existing search.
- *
- * @param object $oSearch Partial search description derived so far.
- * @param object $oPosition Description of the token position within
- the query.
- *
- * @return SearchDescription[] List of derived search descriptions.
- */
- public function extendSearch($oSearch, $oPosition)
- {
- $aNewSearches = array();
-
- // sanity check: if the housenumber is not mainly made
- // up of numbers, add a penalty
- $iSearchCost = 1;
- if (preg_match('/\\d/', $this->sToken) === 0
- || preg_match_all('/[^0-9 ]/', $this->sToken, $aMatches) > 3) {
- $iSearchCost += strlen($this->sToken) - 1;
- }
- if (!$oSearch->hasOperator(\Nominatim\Operator::NONE)) {
- $iSearchCost++;
- }
- if (empty($this->iId)) {
- $iSearchCost++;
- }
- // also must not appear in the middle of the address
- if ($oSearch->hasAddress() || $oSearch->hasPostcode()) {
- $iSearchCost++;
- }
-
- $oNewSearch = $oSearch->clone($iSearchCost);
- $oNewSearch->setHousenumber($this->sToken);
- $aNewSearches[] = $oNewSearch;
-
- // Housenumbers may appear in the name when the place has its own
- // address terms.
- if ($this->iId !== null
- && ($oSearch->getNamePhrase() >= 0 || !$oSearch->hasName())
- && !$oSearch->hasAddress()
- ) {
- $oNewSearch = $oSearch->clone($iSearchCost);
- $oNewSearch->setHousenumberAsName($this->iId);
-
- $aNewSearches[] = $oNewSearch;
- }
-
- return $aNewSearches;
- }
-
-
- public function debugInfo()
- {
- return array(
- 'ID' => $this->iId,
- 'Type' => 'house number',
- 'Info' => array('nr' => $this->sToken)
- );
- }
-
- public function debugCode()
- {
- return 'H';
- }
-}
+++ /dev/null
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-namespace Nominatim;
-
-require_once(CONST_LibDir.'/TokenCountry.php');
-require_once(CONST_LibDir.'/TokenHousenumber.php');
-require_once(CONST_LibDir.'/TokenPostcode.php');
-require_once(CONST_LibDir.'/TokenSpecialTerm.php');
-require_once(CONST_LibDir.'/TokenWord.php');
-require_once(CONST_LibDir.'/TokenPartial.php');
-require_once(CONST_LibDir.'/SpecialSearchOperator.php');
-
-/**
- * Saves information about the tokens that appear in a search query.
- *
- * Tokens are sorted by their normalized form, the token word. There are different
- * kinds of tokens, represented by different Token* classes. Note that
- * tokens do not have a common base class. All tokens need to have a field
- * with the word id that points to an entry in the `word` database table
- * but otherwise the information saved about a token can be very different.
- */
-class TokenList
-{
- // List of list of tokens indexed by their word_token.
- private $aTokens = array();
-
-
- /**
- * Return total number of tokens.
- *
- * @return Integer
- */
- public function count()
- {
- return count($this->aTokens);
- }
-
- /**
- * Check if there are tokens for the given token word.
- *
- * @param string $sWord Token word to look for.
- *
- * @return bool True if there is one or more token for the token word.
- */
- public function contains($sWord)
- {
- return isset($this->aTokens[$sWord]);
- }
-
- /**
- * Check if there are partial or full tokens for the given word.
- *
- * @param string $sWord Token word to look for.
- *
- * @return bool True if there is one or more token for the token word.
- */
- public function containsAny($sWord)
- {
- return isset($this->aTokens[$sWord]);
- }
-
- /**
- * Get the list of tokens for the given token word.
- *
- * @param string $sWord Token word to look for.
- *
- * @return object[] Array of tokens for the given token word or an
- * empty array if no tokens could be found.
- */
- public function get($sWord)
- {
- return isset($this->aTokens[$sWord]) ? $this->aTokens[$sWord] : array();
- }
-
- public function getFullWordIDs()
- {
- $ids = array();
-
- foreach ($this->aTokens as $aTokenList) {
- foreach ($aTokenList as $oToken) {
- if (is_a($oToken, '\Nominatim\Token\Word')) {
- $ids[$oToken->getId()] = $oToken->getId();
- }
- }
- }
-
- return $ids;
- }
-
- /**
- * Add a new token for the given word.
- *
- * @param string $sWord Word the token describes.
- * @param object $oToken Token object to add.
- *
- * @return void
- */
- public function addToken($sWord, $oToken)
- {
- if (isset($this->aTokens[$sWord])) {
- $this->aTokens[$sWord][] = $oToken;
- } else {
- $this->aTokens[$sWord] = array($oToken);
- }
- }
-
- public function debugTokenByWordIdList()
- {
- $aWordsIDs = array();
- foreach ($this->aTokens as $sToken => $aWords) {
- foreach ($aWords as $aToken) {
- $iId = $aToken->getId();
- if ($iId !== null) {
- $aWordsIDs[$iId] = '#'.$sToken.'('.$aToken->debugCode().' '.$iId.')#';
- }
- }
- }
-
- return $aWordsIDs;
- }
-
- public function debugInfo()
- {
- return $this->aTokens;
- }
-}
+++ /dev/null
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-namespace Nominatim\Token;
-
-/**
- * A standard word token.
- */
-class Partial
-{
- /// Database word id, if applicable.
- private $iId;
- /// Number of appearances in the database.
- private $iSearchNameCount;
- /// True, if the token consists exclusively of digits and spaces.
- private $bNumberToken;
-
- public function __construct($iId, $sToken, $iSearchNameCount)
- {
- $this->iId = $iId;
- $this->bNumberToken = (bool) preg_match('#^[0-9 ]+$#', $sToken);
- $this->iSearchNameCount = $iSearchNameCount;
- }
-
- public function getId()
- {
- return $this->iId;
- }
-
- /**
- * Check if the token can be added to the given search.
- * Derive new searches by adding this token to an existing search.
- *
- * @param object $oSearch Partial search description derived so far.
- * @param object $oPosition Description of the token position within
- the query.
- *
- * @return True if the token is compatible with the search configuration
- * given the position.
- */
- public function isExtendable($oSearch, $oPosition)
- {
- return !$oPosition->isPhrase('country');
- }
-
- /**
- * Derive new searches by adding this token to an existing search.
- *
- * @param object $oSearch Partial search description derived so far.
- * @param object $oPosition Description of the token position within
- the query.
- *
- * @return SearchDescription[] List of derived search descriptions.
- */
- public function extendSearch($oSearch, $oPosition)
- {
- $aNewSearches = array();
-
- // Partial token in Address.
- if (($oPosition->isPhrase('') || !$oPosition->isFirstPhrase())
- && $oSearch->hasName()
- ) {
- $iSearchCost = $this->bNumberToken ? 2 : 1;
- if ($this->iSearchNameCount >= CONST_Max_Word_Frequency) {
- $iSearchCost += 1;
- }
-
- $oNewSearch = $oSearch->clone($iSearchCost);
- $oNewSearch->addAddressToken(
- $this->iId,
- $this->iSearchNameCount < CONST_Max_Word_Frequency
- );
-
- $aNewSearches[] = $oNewSearch;
- }
-
- // Partial token in Name.
- if ((!$oSearch->hasPostcode() && !$oSearch->hasAddress())
- && (!$oSearch->hasName(true)
- || $oSearch->getNamePhrase() == $oPosition->getPhrase())
- ) {
- $iSearchCost = 1;
- if (!$oSearch->hasName(true)) {
- $iSearchCost += 1;
- }
- if ($this->bNumberToken) {
- $iSearchCost += 1;
- }
-
- $oNewSearch = $oSearch->clone($iSearchCost);
- $oNewSearch->addPartialNameToken(
- $this->iId,
- $this->iSearchNameCount < CONST_Max_Word_Frequency,
- $this->iSearchNameCount > CONST_Search_NameOnlySearchFrequencyThreshold,
- $oPosition->getPhrase()
- );
-
- $aNewSearches[] = $oNewSearch;
- }
-
- return $aNewSearches;
- }
-
-
- public function debugInfo()
- {
- return array(
- 'ID' => $this->iId,
- 'Type' => 'partial',
- 'Info' => array(
- 'count' => $this->iSearchNameCount
- )
- );
- }
-
- public function debugCode()
- {
- return 'w';
- }
-}
+++ /dev/null
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-namespace Nominatim\Token;
-
-/**
- * A postcode token.
- */
-class Postcode
-{
- /// Database word id, if available.
- private $iId;
- /// Full normalized postcode (upper cased).
- private $sPostcode;
- // Optional country code the postcode belongs to (currently unused).
- private $sCountryCode;
-
- public function __construct($iId, $sPostcode, $sCountryCode = '')
- {
- $this->iId = $iId;
- $iSplitPos = strpos($sPostcode, '@');
- if ($iSplitPos === false) {
- $this->sPostcode = $sPostcode;
- } else {
- $this->sPostcode = substr($sPostcode, 0, $iSplitPos);
- }
- $this->sCountryCode = empty($sCountryCode) ? '' : $sCountryCode;
- }
-
- public function getId()
- {
- return $this->iId;
- }
-
- /**
- * Check if the token can be added to the given search.
- * Derive new searches by adding this token to an existing search.
- *
- * @param object $oSearch Partial search description derived so far.
- * @param object $oPosition Description of the token position within
- the query.
- *
- * @return True if the token is compatible with the search configuration
- * given the position.
- */
- public function isExtendable($oSearch, $oPosition)
- {
- return !$oSearch->hasPostcode() && $oPosition->maybePhrase('postalcode');
- }
-
- /**
- * Derive new searches by adding this token to an existing search.
- *
- * @param object $oSearch Partial search description derived so far.
- * @param object $oPosition Description of the token position within
- the query.
- *
- * @return SearchDescription[] List of derived search descriptions.
- */
- public function extendSearch($oSearch, $oPosition)
- {
- $aNewSearches = array();
-
- // If we have structured search or this is the first term,
- // make the postcode the primary search element.
- if ($oSearch->hasOperator(\Nominatim\Operator::NONE) && $oPosition->isFirstToken()) {
- $oNewSearch = $oSearch->clone(1);
- $oNewSearch->setPostcodeAsName($this->iId, $this->sPostcode);
-
- $aNewSearches[] = $oNewSearch;
- }
-
- // If we have a structured search or this is not the first term,
- // add the postcode as an addendum.
- if (!$oSearch->hasOperator(\Nominatim\Operator::POSTCODE)
- && ($oPosition->isPhrase('postalcode') || $oSearch->hasName())
- ) {
- $iPenalty = 1;
- if (strlen($this->sPostcode) < 4) {
- $iPenalty += 4 - strlen($this->sPostcode);
- }
- $oNewSearch = $oSearch->clone($iPenalty);
- $oNewSearch->setPostcode($this->sPostcode);
-
- $aNewSearches[] = $oNewSearch;
- }
-
- return $aNewSearches;
- }
-
- public function debugInfo()
- {
- return array(
- 'ID' => $this->iId,
- 'Type' => 'postcode',
- 'Info' => $this->sPostcode.'('.$this->sCountryCode.')'
- );
- }
-
- public function debugCode()
- {
- return 'P';
- }
-}
+++ /dev/null
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-namespace Nominatim\Token;
-
-require_once(CONST_LibDir.'/SpecialSearchOperator.php');
-
-/**
- * A word token describing a place type.
- */
-class SpecialTerm
-{
- /// Database word id, if applicable.
- private $iId;
- /// Class (or OSM tag key) of the place to look for.
- private $sClass;
- /// Type (or OSM tag value) of the place to look for.
- private $sType;
- /// Relationship of the operator to the object (see Operator class).
- private $iOperator;
-
- public function __construct($iID, $sClass, $sType, $iOperator)
- {
- $this->iId = $iID;
- $this->sClass = $sClass;
- $this->sType = $sType;
- $this->iOperator = $iOperator;
- }
-
- public function getId()
- {
- return $this->iId;
- }
-
- /**
- * Check if the token can be added to the given search.
- * Derive new searches by adding this token to an existing search.
- *
- * @param object $oSearch Partial search description derived so far.
- * @param object $oPosition Description of the token position within
- the query.
- *
- * @return True if the token is compatible with the search configuration
- * given the position.
- */
- public function isExtendable($oSearch, $oPosition)
- {
- return !$oSearch->hasOperator()
- && $oPosition->isPhrase('')
- && ($this->iOperator != \Nominatim\Operator::NONE
- || (!$oSearch->hasAddress() && !$oSearch->hasHousenumber() && !$oSearch->hasCountry()));
- }
-
- /**
- * Derive new searches by adding this token to an existing search.
- *
- * @param object $oSearch Partial search description derived so far.
- * @param object $oPosition Description of the token position within
- the query.
- *
- * @return SearchDescription[] List of derived search descriptions.
- */
- public function extendSearch($oSearch, $oPosition)
- {
- $iSearchCost = 0;
-
- $iOp = $this->iOperator;
- if ($iOp == \Nominatim\Operator::NONE) {
- if ($oPosition->isFirstToken()
- || $oSearch->hasName()
- || $oSearch->getContext()->isBoundedSearch()
- ) {
- $iOp = \Nominatim\Operator::NAME;
- $iSearchCost += 3;
- } else {
- $iOp = \Nominatim\Operator::NEAR;
- $iSearchCost += 4;
- if (!$oPosition->isFirstToken()) {
- $iSearchCost += 3;
- }
- }
- } elseif ($oPosition->isFirstToken()) {
- $iSearchCost += 2;
- } elseif ($oPosition->isLastToken()) {
- $iSearchCost += 4;
- } else {
- $iSearchCost += 6;
- }
-
- if ($oSearch->hasHousenumber()) {
- $iSearchCost ++;
- }
-
- $oNewSearch = $oSearch->clone($iSearchCost);
- $oNewSearch->setPoiSearch($iOp, $this->sClass, $this->sType);
-
- return array($oNewSearch);
- }
-
-
- public function debugInfo()
- {
- return array(
- 'ID' => $this->iId,
- 'Type' => 'special term',
- 'Info' => array(
- 'class' => $this->sClass,
- 'type' => $this->sType,
- 'operator' => \Nominatim\Operator::toString($this->iOperator)
- )
- );
- }
-
- public function debugCode()
- {
- return 'S';
- }
-}
+++ /dev/null
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-namespace Nominatim\Token;
-
-/**
- * A standard word token.
- */
-class Word
-{
- /// Database word id, if applicable.
- private $iId;
- /// Number of appearances in the database.
- private $iSearchNameCount;
- /// Number of terms in the word.
- private $iTermCount;
-
- public function __construct($iId, $iSearchNameCount, $iTermCount)
- {
- $this->iId = $iId;
- $this->iSearchNameCount = $iSearchNameCount;
- $this->iTermCount = $iTermCount;
- }
-
- public function getId()
- {
- return $this->iId;
- }
-
- /**
- * Check if the token can be added to the given search.
- * Derive new searches by adding this token to an existing search.
- *
- * @param object $oSearch Partial search description derived so far.
- * @param object $oPosition Description of the token position within
- the query.
- *
- * @return True if the token is compatible with the search configuration
- * given the position.
- */
- public function isExtendable($oSearch, $oPosition)
- {
- return !$oPosition->isPhrase('country');
- }
-
- /**
- * Derive new searches by adding this token to an existing search.
- *
- * @param object $oSearch Partial search description derived so far.
- * @param object $oPosition Description of the token position within
- the query.
- *
- * @return SearchDescription[] List of derived search descriptions.
- */
- public function extendSearch($oSearch, $oPosition)
- {
- // Full words can only be a name if they appear at the beginning
- // of the phrase. In structured search the name must forcibly in
- // the first phrase. In unstructured search it may be in a later
- // phrase when the first phrase is a house number.
- if ($oSearch->hasName()
- || !($oPosition->isFirstPhrase() || $oPosition->isPhrase(''))
- ) {
- if ($this->iTermCount > 1
- && ($oPosition->isPhrase('') || !$oPosition->isFirstPhrase())
- ) {
- $oNewSearch = $oSearch->clone(1);
- $oNewSearch->addAddressToken($this->iId);
-
- return array($oNewSearch);
- }
- } elseif (!$oSearch->hasName(true)) {
- $oNewSearch = $oSearch->clone(1);
- $oNewSearch->addNameToken(
- $this->iId,
- CONST_Search_NameOnlySearchFrequencyThreshold
- && $this->iSearchNameCount
- < CONST_Search_NameOnlySearchFrequencyThreshold
- );
-
- return array($oNewSearch);
- }
-
- return array();
- }
-
- public function debugInfo()
- {
- return array(
- 'ID' => $this->iId,
- 'Type' => 'word',
- 'Info' => array(
- 'count' => $this->iSearchNameCount,
- 'terms' => $this->iTermCount
- )
- );
- }
-
- public function debugCode()
- {
- return 'W';
- }
-}
+++ /dev/null
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-require_once(CONST_LibDir.'/Shell.php');
-
-function getCmdOpt($aArg, $aSpec, &$aResult, $bExitOnError = false, $bExitOnUnknown = false)
-{
- $aQuick = array();
- $aCounts = array();
-
- foreach ($aSpec as $aLine) {
- if (is_array($aLine)) {
- if ($aLine[0]) {
- $aQuick['--'.$aLine[0]] = $aLine;
- }
- if ($aLine[1]) {
- $aQuick['-'.$aLine[1]] = $aLine;
- }
- $aCounts[$aLine[0]] = 0;
- }
- }
-
- $aResult = array();
- $bUnknown = false;
- $iSize = count($aArg);
- for ($i = 1; $i < $iSize; $i++) {
- if (isset($aQuick[$aArg[$i]])) {
- $aLine = $aQuick[$aArg[$i]];
- $aCounts[$aLine[0]]++;
- $xVal = null;
- if ($aLine[4] == $aLine[5]) {
- if ($aLine[4]) {
- $xVal = array();
- for ($n = $aLine[4]; $i < $iSize && $n; $n--) {
- $i++;
- if ($i >= $iSize || $aArg[$i][0] == '-') {
- showUsage($aSpec, $bExitOnError, 'Parameter of \''.$aLine[0].'\' is missing');
- }
-
- switch ($aLine[6]) {
- case 'realpath':
- $xVal[] = realpath($aArg[$i]);
- break;
- case 'realdir':
- $sPath = realpath(dirname($aArg[$i]));
- if ($sPath) {
- $xVal[] = $sPath . '/' . basename($aArg[$i]);
- } else {
- $xVal[] = $sPath;
- }
- break;
- case 'bool':
- $xVal[] = (bool)$aArg[$i];
- break;
- case 'int':
- $xVal[] = (int)$aArg[$i];
- break;
- case 'float':
- $xVal[] = (float)$aArg[$i];
- break;
- default:
- $xVal[] = $aArg[$i];
- break;
- }
- }
- if ($aLine[4] == 1) {
- $xVal = $xVal[0];
- }
- } else {
- $xVal = true;
- }
- } else {
- fail('Variable numbers of params not yet supported');
- }
-
- if ($aLine[3] > 1) {
- if (!array_key_exists($aLine[0], $aResult)) {
- $aResult[$aLine[0]] = array();
- }
- $aResult[$aLine[0]][] = $xVal;
- } else {
- $aResult[$aLine[0]] = $xVal;
- }
- } else {
- $bUnknown = $aArg[$i];
- }
- }
-
- if (array_key_exists('help', $aResult)) {
- showUsage($aSpec);
- }
- if ($bUnknown && $bExitOnUnknown) {
- showUsage($aSpec, $bExitOnError, 'Unknown option \''.$bUnknown.'\'');
- }
-
- foreach ($aSpec as $aLine) {
- if (is_array($aLine)) {
- if ($aCounts[$aLine[0]] < $aLine[2]) {
- showUsage($aSpec, $bExitOnError, 'Option \''.$aLine[0].'\' is missing');
- }
- if ($aCounts[$aLine[0]] > $aLine[3]) {
- showUsage($aSpec, $bExitOnError, 'Option \''.$aLine[0].'\' is present too many times');
- }
- if ($aLine[6] == 'bool' && !array_key_exists($aLine[0], $aResult)) {
- $aResult[$aLine[0]] = false;
- }
- }
- }
- return $bUnknown;
-}
-
-function showUsage($aSpec, $bExit = false, $sError = false)
-{
- if ($sError) {
- echo basename($_SERVER['argv'][0]).': '.$sError."\n";
- echo 'Try `'.basename($_SERVER['argv'][0]).' --help` for more information.'."\n";
- exit;
- }
- echo 'Usage: '.basename($_SERVER['argv'][0])."\n";
- $bFirst = true;
- foreach ($aSpec as $aLine) {
- if (is_array($aLine)) {
- if ($bFirst) {
- $bFirst = false;
- echo "\n";
- }
- $aNames = array();
- if ($aLine[1]) {
- $aNames[] = '-'.$aLine[1];
- }
- if ($aLine[0]) {
- $aNames[] = '--'.$aLine[0];
- }
- $sName = join(', ', $aNames);
- echo ' '.$sName.str_repeat(' ', 30-strlen($sName)).$aLine[7]."\n";
- } else {
- echo $aLine."\n";
- }
- }
- echo "\n";
- exit;
-}
-
-function info($sMsg)
-{
- echo date('Y-m-d H:i:s == ').$sMsg."\n";
-}
-
-$aWarnings = array();
-
-
-function warn($sMsg)
-{
- $GLOBALS['aWarnings'][] = $sMsg;
- echo date('Y-m-d H:i:s == ').'WARNING: '.$sMsg."\n";
-}
-
-
-function repeatWarnings()
-{
- foreach ($GLOBALS['aWarnings'] as $sMsg) {
- echo ' * ',$sMsg."\n";
- }
-}
-
-
-function setupHTTPProxy()
-{
- if (!getSettingBool('HTTP_PROXY')) {
- return;
- }
-
- $sProxy = 'tcp://'.getSetting('HTTP_PROXY_HOST').':'.getSetting('HTTP_PROXY_PROT');
- $aHeaders = array();
-
- $sLogin = getSetting('HTTP_PROXY_LOGIN');
- $sPassword = getSetting('HTTP_PROXY_PASSWORD');
-
- if ($sLogin && $sPassword) {
- $sAuth = base64_encode($sLogin.':'.$sPassword);
- $aHeaders = array('Proxy-Authorization: Basic '.$sAuth);
- }
-
- $aProxyHeader = array(
- 'proxy' => $sProxy,
- 'request_fulluri' => true,
- 'header' => $aHeaders
- );
-
- $aContext = array('http' => $aProxyHeader, 'https' => $aProxyHeader);
- stream_context_set_default($aContext);
-}
+++ /dev/null
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-require('Symfony/Component/Dotenv/autoload.php');
-
-function loadDotEnv()
-{
- $dotenv = new \Symfony\Component\Dotenv\Dotenv();
- $dotenv->load(CONST_ConfigDir.'/env.defaults');
-
- if (file_exists('.env')) {
- $dotenv->load('.env');
- }
-}
+++ /dev/null
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-require_once('init.php');
-require_once('cmd.php');
-require_once('DebugNone.php');
+++ /dev/null
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-require_once('init.php');
-require_once('ParameterParser.php');
-require_once(CONST_Debug ? 'DebugHtml.php' : 'DebugNone.php');
-
-/***************************************************************************
- *
- * Error handling functions
- *
- */
-
-function userError($sMsg)
-{
- throw new \Exception($sMsg, 400);
-}
-
-
-function exception_handler_json($exception)
-{
- http_response_code($exception->getCode() == 0 ? 500 : $exception->getCode());
- header('Content-type: application/json; charset=utf-8');
- include(CONST_LibDir.'/template/error-json.php');
- exit();
-}
-
-function exception_handler_xml($exception)
-{
- http_response_code($exception->getCode() == 0 ? 500 : $exception->getCode());
- header('Content-type: text/xml; charset=utf-8');
- echo '<?xml version="1.0" encoding="UTF-8" ?>'."\n";
- include(CONST_LibDir.'/template/error-xml.php');
- exit();
-}
-
-function shutdown_exception_handler_xml()
-{
- $error = error_get_last();
- if ($error !== null && $error['type'] === E_ERROR) {
- exception_handler_xml(new \Exception($error['message'], 500));
- }
-}
-
-function shutdown_exception_handler_json()
-{
- $error = error_get_last();
- if ($error !== null && $error['type'] === E_ERROR) {
- exception_handler_json(new \Exception($error['message'], 500));
- }
-}
-
-
-function set_exception_handler_by_format($sFormat = null)
-{
- // Multiple calls to register_shutdown_function will cause multiple callbacks
- // to be executed, we only want the last executed. Thus we don't want to register
- // one by default without an explicit $sFormat set.
-
- if (!isset($sFormat)) {
- set_exception_handler('exception_handler_json');
- } elseif ($sFormat == 'xml') {
- set_exception_handler('exception_handler_xml');
- register_shutdown_function('shutdown_exception_handler_xml');
- } else {
- set_exception_handler('exception_handler_json');
- register_shutdown_function('shutdown_exception_handler_json');
- }
-}
-// set a default
-set_exception_handler_by_format();
-
-
-/***************************************************************************
- * HTTP Reply header setup
- */
-
-if (CONST_NoAccessControl) {
- header('Access-Control-Allow-Origin: *');
- header('Access-Control-Allow-Methods: OPTIONS,GET');
- if (!empty($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS'])) {
- header('Access-Control-Allow-Headers: '.$_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS']);
- }
-}
-if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] == 'OPTIONS') {
- exit;
-}
-
-if (CONST_Debug) {
- header('Content-type: text/html; charset=utf-8');
-}
+++ /dev/null
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-require_once(CONST_LibDir.'/lib.php');
-require_once(CONST_LibDir.'/DB.php');
+++ /dev/null
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-function loadSettings($sProjectDir)
-{
- @define('CONST_InstallDir', $sProjectDir);
- // Temporary hack to set the directory via environment instead of
- // the installed scripts. Neither setting is part of the official
- // set of settings.
- defined('CONST_ConfigDir') or define('CONST_ConfigDir', $_SERVER['NOMINATIM_CONFIGDIR']);
-}
-
-function getSetting($sConfName, $sDefault = null)
-{
- $sValue = $_SERVER['NOMINATIM_'.$sConfName];
-
- if ($sDefault !== null && !$sValue) {
- return $sDefault;
- }
-
- return $sValue;
-}
-
-function getSettingBool($sConfName)
-{
- $sVal = strtolower(getSetting($sConfName));
-
- return strcmp($sVal, 'yes') == 0
- || strcmp($sVal, 'true') == 0
- || strcmp($sVal, '1') == 0;
-}
-
-function fail($sError, $sUserError = false)
-{
- if (!$sUserError) {
- $sUserError = $sError;
- }
- error_log('ERROR: '.$sError);
- var_dump($sUserError);
- echo "\n";
- exit(-1);
-}
-
-
-function getProcessorCount()
-{
- $sCPU = file_get_contents('/proc/cpuinfo');
- preg_match_all('#processor\s+: [0-9]+#', $sCPU, $aMatches);
- return count($aMatches[0]);
-}
-
-
-function getTotalMemoryMB()
-{
- $sCPU = file_get_contents('/proc/meminfo');
- preg_match('#MemTotal: +([0-9]+) kB#', $sCPU, $aMatches);
- return (int)($aMatches[1]/1024);
-}
-
-
-function getCacheMemoryMB()
-{
- $sCPU = file_get_contents('/proc/meminfo');
- preg_match('#Cached: +([0-9]+) kB#', $sCPU, $aMatches);
- return (int)($aMatches[1]/1024);
-}
-
-function getDatabaseDate(&$oDB)
-{
- // Find the newest node in the DB
- $iLastOSMID = $oDB->getOne("select max(osm_id) from place where osm_type = 'N'");
- // Lookup the timestamp that node was created
- $sLastNodeURL = 'https://www.openstreetmap.org/api/0.6/node/'.$iLastOSMID.'/1';
- $sLastNodeXML = file_get_contents($sLastNodeURL);
-
- if ($sLastNodeXML === false) {
- return false;
- }
-
- preg_match('#timestamp="(([0-9]{4})-([0-9]{2})-([0-9]{2})T([0-9]{2}):([0-9]{2}):([0-9]{2})Z)"#', $sLastNodeXML, $aLastNodeDate);
-
- return $aLastNodeDate[1];
-}
-
-
-function byImportance($a, $b)
-{
- if ($a['importance'] != $b['importance']) {
- return ($a['importance'] > $b['importance']?-1:1);
- }
-
- return $a['foundorder'] <=> $b['foundorder'];
-}
-
-
-function javascript_renderData($xVal, $iOptions = 0)
-{
- $sCallback = isset($_GET['json_callback']) ? $_GET['json_callback'] : '';
- if ($sCallback && !preg_match('/^[$_\p{L}][$_\p{L}\p{Nd}.[\]]*$/u', $sCallback)) {
- // Unset, we call javascript_renderData again during exception handling
- unset($_GET['json_callback']);
- throw new Exception('Invalid json_callback value', 400);
- }
-
- $iOptions |= JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES;
- if (isset($_GET['pretty']) && in_array(strtolower($_GET['pretty']), array('1', 'true'))) {
- $iOptions |= JSON_PRETTY_PRINT;
- }
-
- $jsonout = json_encode($xVal, $iOptions);
-
- if ($sCallback) {
- header('Content-Type: application/javascript; charset=UTF-8');
- echo $_GET['json_callback'].'('.$jsonout.')';
- } else {
- header('Content-Type: application/json; charset=UTF-8');
- echo $jsonout;
- }
-}
-
-function addQuotes($s)
-{
- return "'".$s."'";
-}
-
-function parseLatLon($sQuery)
-{
- $sFound = null;
- $fQueryLat = null;
- $fQueryLon = null;
-
- if (preg_match('/\\s*([NS])[\s]+([0-9]+[0-9.]*)[°\s]+([0-9.]+)?[′\']*[,\s]+([EW])[\s]+([0-9]+)[°\s]+([0-9]+[0-9.]*)[′\']*\\s*/', $sQuery, $aData)) {
- /* 1 2 3 4 5 6
- * degrees decimal minutes
- * N 40 26.767, W 79 58.933
- * N 40°26.767′, W 79°58.933′
- */
- $sFound = $aData[0];
- $fQueryLat = ($aData[1]=='N'?1:-1) * ($aData[2] + $aData[3]/60);
- $fQueryLon = ($aData[4]=='E'?1:-1) * ($aData[5] + $aData[6]/60);
- } elseif (preg_match('/\\s*([0-9]+)[°\s]+([0-9]+[0-9.]*)?[′\']*[\s]+([NS])[,\s]+([0-9]+)[°\s]+([0-9]+[0-9.]*)?[′\'\s]+([EW])\\s*/', $sQuery, $aData)) {
- /* 1 2 3 4 5 6
- * degrees decimal minutes
- * 40 26.767 N, 79 58.933 W
- * 40° 26.767′ N 79° 58.933′ W
- */
- $sFound = $aData[0];
- $fQueryLat = ($aData[3]=='N'?1:-1) * ($aData[1] + $aData[2]/60);
- $fQueryLon = ($aData[6]=='E'?1:-1) * ($aData[4] + $aData[5]/60);
- } elseif (preg_match('/\\s*([NS])[\s]+([0-9]+)[°\s]+([0-9]+)[′\'\s]+([0-9]+)[″"]*[,\s]+([EW])[\s]+([0-9]+)[°\s]+([0-9]+)[′\'\s]+([0-9]+)[″"]*\\s*/', $sQuery, $aData)) {
- /* 1 2 3 4 5 6 7 8
- * degrees decimal seconds
- * N 40 26 46 W 79 58 56
- * N 40° 26′ 46″, W 79° 58′ 56″
- */
- $sFound = $aData[0];
- $fQueryLat = ($aData[1]=='N'?1:-1) * ($aData[2] + $aData[3]/60 + $aData[4]/3600);
- $fQueryLon = ($aData[5]=='E'?1:-1) * ($aData[6] + $aData[7]/60 + $aData[8]/3600);
- } elseif (preg_match('/\\s*([0-9]+)[°\s]+([0-9]+)[′\'\s]+([0-9]+[0-9.]*)[″"\s]+([NS])[,\s]+([0-9]+)[°\s]+([0-9]+)[′\'\s]+([0-9]+[0-9.]*)[″"\s]+([EW])\\s*/', $sQuery, $aData)) {
- /* 1 2 3 4 5 6 7 8
- * degrees decimal seconds
- * 40 26 46 N 79 58 56 W
- * 40° 26′ 46″ N, 79° 58′ 56″ W
- * 40° 26′ 46.78″ N, 79° 58′ 56.89″ W
- */
- $sFound = $aData[0];
- $fQueryLat = ($aData[4]=='N'?1:-1) * ($aData[1] + $aData[2]/60 + $aData[3]/3600);
- $fQueryLon = ($aData[8]=='E'?1:-1) * ($aData[5] + $aData[6]/60 + $aData[7]/3600);
- } elseif (preg_match('/\\s*([NS])[\s]+([0-9]+[0-9]*\\.[0-9]+)[°]*[,\s]+([EW])[\s]+([0-9]+[0-9]*\\.[0-9]+)[°]*\\s*/', $sQuery, $aData)) {
- /* 1 2 3 4
- * degrees decimal
- * N 40.446° W 79.982°
- */
- $sFound = $aData[0];
- $fQueryLat = ($aData[1]=='N'?1:-1) * ($aData[2]);
- $fQueryLon = ($aData[3]=='E'?1:-1) * ($aData[4]);
- } elseif (preg_match('/\\s*([0-9]+[0-9]*\\.[0-9]+)[°\s]+([NS])[,\s]+([0-9]+[0-9]*\\.[0-9]+)[°\s]+([EW])\\s*/', $sQuery, $aData)) {
- /* 1 2 3 4
- * degrees decimal
- * 40.446° N 79.982° W
- */
- $sFound = $aData[0];
- $fQueryLat = ($aData[2]=='N'?1:-1) * ($aData[1]);
- $fQueryLon = ($aData[4]=='E'?1:-1) * ($aData[3]);
- } elseif (preg_match('/(\\s*\\[|^\\s*|\\s*)(-?[0-9]+[0-9]*\\.[0-9]+)[,\s]+(-?[0-9]+[0-9]*\\.[0-9]+)(\\]\\s*|\\s*$|\\s*)/', $sQuery, $aData)) {
- /* 1 2 3 4
- * degrees decimal
- * 12.34, 56.78
- * 12.34 56.78
- * [12.456,-78.90]
- */
- $sFound = $aData[0];
- $fQueryLat = $aData[2];
- $fQueryLon = $aData[3];
- } else {
- return false;
- }
-
- return array($sFound, $fQueryLat, $fQueryLon);
-}
-
-function addressRankToGeocodeJsonType($iAddressRank)
-{
- if ($iAddressRank >= 29 && $iAddressRank <= 30) {
- return 'house';
- }
- if ($iAddressRank >= 26 && $iAddressRank < 28) {
- return 'street';
- }
- if ($iAddressRank >= 22 && $iAddressRank < 26) {
- return 'locality';
- }
- if ($iAddressRank >= 17 && $iAddressRank < 22) {
- return 'district';
- }
- if ($iAddressRank >= 13 && $iAddressRank < 17) {
- return 'city';
- }
- if ($iAddressRank >= 10 && $iAddressRank < 13) {
- return 'county';
- }
- if ($iAddressRank >= 5 && $iAddressRank < 10) {
- return 'state';
- }
- if ($iAddressRank >= 4 && $iAddressRank < 5) {
- return 'country';
- }
-
- return 'locality';
-}
-
-if (!function_exists('array_key_last')) {
- function array_key_last(array $array)
- {
- if (!empty($array)) {
- return key(array_slice($array, -1, 1, true));
- }
- }
-}
+++ /dev/null
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-
-function logStart(&$oDB, $sType = '', $sQuery = '', $aLanguageList = array())
-{
- $fStartTime = microtime(true);
- $aStartTime = explode('.', $fStartTime);
- if (!isset($aStartTime[1])) {
- $aStartTime[1] = '0';
- }
-
- $sOutputFormat = '';
- if (isset($_GET['format'])) {
- $sOutputFormat = $_GET['format'];
- }
-
- if ($sType == 'reverse') {
- $sOutQuery = (isset($_GET['lat'])?$_GET['lat']:'').'/';
- if (isset($_GET['lon'])) {
- $sOutQuery .= $_GET['lon'];
- }
- if (isset($_GET['zoom'])) {
- $sOutQuery .= '/'.$_GET['zoom'];
- }
- } else {
- $sOutQuery = $sQuery;
- }
-
- $hLog = array(
- date('Y-m-d H:i:s', $aStartTime[0]).'.'.$aStartTime[1],
- $_SERVER['REMOTE_ADDR'],
- $_SERVER['QUERY_STRING'],
- $sOutQuery,
- $sType,
- $fStartTime
- );
-
- if (CONST_Log_DB) {
- if (isset($_GET['email'])) {
- $sUserAgent = $_GET['email'];
- } elseif (isset($_SERVER['HTTP_REFERER'])) {
- $sUserAgent = $_SERVER['HTTP_REFERER'];
- } elseif (isset($_SERVER['HTTP_USER_AGENT'])) {
- $sUserAgent = $_SERVER['HTTP_USER_AGENT'];
- } else {
- $sUserAgent = '';
- }
- $sSQL = 'insert into new_query_log (type,starttime,query,ipaddress,useragent,language,format,searchterm)';
- $sSQL .= ' values (';
- $sSQL .= join(',', $oDB->getDBQuotedList(array(
- $sType,
- $hLog[0],
- $hLog[2],
- $hLog[1],
- $sUserAgent,
- join(',', $aLanguageList),
- $sOutputFormat,
- $hLog[3]
- )));
- $sSQL .= ')';
- $oDB->exec($sSQL);
- }
-
- return $hLog;
-}
-
-function logEnd(&$oDB, $hLog, $iNumResults)
-{
- $fEndTime = microtime(true);
-
- if (CONST_Log_DB) {
- $aEndTime = explode('.', $fEndTime);
- if (!isset($aEndTime[1])) {
- $aEndTime[1] = '0';
- }
- $sEndTime = date('Y-m-d H:i:s', $aEndTime[0]).'.'.$aEndTime[1];
-
- $sSQL = 'update new_query_log set endtime = '.$oDB->getDBQuoted($sEndTime).', results = '.$iNumResults;
- $sSQL .= ' where starttime = '.$oDB->getDBQuoted($hLog[0]);
- $sSQL .= ' and ipaddress = '.$oDB->getDBQuoted($hLog[1]);
- $sSQL .= ' and query = '.$oDB->getDBQuoted($hLog[2]);
- $oDB->exec($sSQL);
- }
-
- if (CONST_Log_File) {
- $aOutdata = sprintf(
- "[%s] %.4f %d %s \"%s\"\n",
- $hLog[0],
- $fEndTime-$hLog[5],
- $iNumResults,
- $hLog[4],
- $hLog[2]
- );
- file_put_contents(CONST_Log_File, $aOutdata, FILE_APPEND | LOCK_EX);
- }
-}
+++ /dev/null
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-
-function formatOSMType($sType, $bIncludeExternal = true)
-{
- if ($sType == 'N') {
- return 'node';
- }
- if ($sType == 'W') {
- return 'way';
- }
- if ($sType == 'R') {
- return 'relation';
- }
-
- if (!$bIncludeExternal) {
- return '';
- }
-
- if ($sType == 'T') {
- return 'way';
- }
- if ($sType == 'I') {
- return 'way';
- }
-
- // not handled: P, L
-
- return '';
-}
+++ /dev/null
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-function getOsm2pgsqlBinary()
-{
- $sBinary = getSetting('OSM2PGSQL_BINARY');
-
- return $sBinary ? $sBinary : CONST_Default_Osm2pgsql;
-}
-
-function getImportStyle()
-{
- $sStyle = getSetting('IMPORT_STYLE');
-
- if (in_array($sStyle, array('admin', 'street', 'address', 'full', 'extratags'))) {
- return CONST_ConfigDir.'/import-'.$sStyle.'.style';
- }
-
- return $sStyle;
-}
+++ /dev/null
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-// https://github.com/geocoders/geocodejson-spec/
-
-$aFilteredPlaces = array();
-
-if (empty($aPlace)) {
- if (isset($sError)) {
- $aFilteredPlaces['error'] = $sError;
- } else {
- $aFilteredPlaces['error'] = 'Unable to geocode';
- }
- javascript_renderData($aFilteredPlaces);
-} else {
- $aFilteredPlaces = array(
- 'type' => 'Feature',
- 'properties' => array(
- 'geocoding' => array()
- )
- );
-
- if (isset($aPlace['place_id'])) {
- $aFilteredPlaces['properties']['geocoding']['place_id'] = $aPlace['place_id'];
- }
- $sOSMType = formatOSMType($aPlace['osm_type']);
- if ($sOSMType) {
- $aFilteredPlaces['properties']['geocoding']['osm_type'] = $sOSMType;
- $aFilteredPlaces['properties']['geocoding']['osm_id'] = $aPlace['osm_id'];
- }
-
- $aFilteredPlaces['properties']['geocoding']['osm_key'] = $aPlace['class'];
- $aFilteredPlaces['properties']['geocoding']['osm_value'] = $aPlace['type'];
-
- $aFilteredPlaces['properties']['geocoding']['type'] = addressRankToGeocodeJsonType($aPlace['rank_address']);
-
- $aFilteredPlaces['properties']['geocoding']['accuracy'] = (int) $fDistance;
-
- $aFilteredPlaces['properties']['geocoding']['label'] = $aPlace['langaddress'];
-
- if ($aPlace['placename'] !== null) {
- $aFilteredPlaces['properties']['geocoding']['name'] = $aPlace['placename'];
- }
-
- if (isset($aPlace['address'])) {
- $aPlace['address']->addGeocodeJsonAddressParts(
- $aFilteredPlaces['properties']['geocoding']
- );
-
- $aFilteredPlaces['properties']['geocoding']['admin']
- = $aPlace['address']->getAdminLevels();
- }
-
- if (isset($aPlace['asgeojson'])) {
- $aFilteredPlaces['geometry'] = json_decode($aPlace['asgeojson'], true);
- } else {
- $aFilteredPlaces['geometry'] = array(
- 'type' => 'Point',
- 'coordinates' => array(
- (float) $aPlace['lon'],
- (float) $aPlace['lat']
- )
- );
- }
-
- javascript_renderData(array(
- 'type' => 'FeatureCollection',
- 'geocoding' => array(
- 'version' => '0.1.0',
- 'attribution' => 'Data © OpenStreetMap contributors, ODbL 1.0. https://osm.org/copyright',
- 'licence' => 'ODbL',
- 'query' => $sQuery
- ),
- 'features' => array($aFilteredPlaces)
- ));
-}
+++ /dev/null
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-$aFilteredPlaces = array();
-
-if (empty($aPlace)) {
- if (isset($sError)) {
- $aFilteredPlaces['error'] = $sError;
- } else {
- $aFilteredPlaces['error'] = 'Unable to geocode';
- }
- javascript_renderData($aFilteredPlaces);
-} else {
- $aFilteredPlaces = array(
- 'type' => 'Feature',
- 'properties' => array()
- );
-
- if (isset($aPlace['place_id'])) {
- $aFilteredPlaces['properties']['place_id'] = $aPlace['place_id'];
- }
- $sOSMType = formatOSMType($aPlace['osm_type']);
- if ($sOSMType) {
- $aFilteredPlaces['properties']['osm_type'] = $sOSMType;
- $aFilteredPlaces['properties']['osm_id'] = $aPlace['osm_id'];
- }
-
- $aFilteredPlaces['properties']['place_rank'] = $aPlace['rank_search'];
-
- $aFilteredPlaces['properties']['category'] = $aPlace['class'];
- $aFilteredPlaces['properties']['type'] = $aPlace['type'];
-
- $aFilteredPlaces['properties']['importance'] = $aPlace['importance'];
-
- $aFilteredPlaces['properties']['addresstype'] = strtolower($aPlace['addresstype']);
-
- $aFilteredPlaces['properties']['name'] = $aPlace['placename'];
-
- $aFilteredPlaces['properties']['display_name'] = $aPlace['langaddress'];
-
- if (isset($aPlace['address'])) {
- $aFilteredPlaces['properties']['address'] = $aPlace['address']->getAddressNames();
- }
- if (isset($aPlace['sExtraTags'])) {
- $aFilteredPlaces['properties']['extratags'] = $aPlace['sExtraTags'];
- }
- if (isset($aPlace['sNameDetails'])) {
- $aFilteredPlaces['properties']['namedetails'] = $aPlace['sNameDetails'];
- }
-
- if (isset($aPlace['aBoundingBox'])) {
- $aFilteredPlaces['bbox'] = array(
- (float) $aPlace['aBoundingBox'][2], // minlon
- (float) $aPlace['aBoundingBox'][0], // minlat
- (float) $aPlace['aBoundingBox'][3], // maxlon
- (float) $aPlace['aBoundingBox'][1] // maxlat
- );
- }
-
- if (isset($aPlace['asgeojson'])) {
- $aFilteredPlaces['geometry'] = json_decode($aPlace['asgeojson'], true);
- } else {
- $aFilteredPlaces['geometry'] = array(
- 'type' => 'Point',
- 'coordinates' => array(
- (float) $aPlace['lon'],
- (float) $aPlace['lat']
- )
- );
- }
-
-
- javascript_renderData(array(
- 'type' => 'FeatureCollection',
- 'licence' => 'Data © OpenStreetMap contributors, ODbL 1.0. https://osm.org/copyright',
- 'features' => array($aFilteredPlaces)
- ));
-}
+++ /dev/null
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-$aFilteredPlaces = array();
-
-if (empty($aPlace)) {
- if (isset($sError)) {
- $aFilteredPlaces['error'] = $sError;
- } else {
- $aFilteredPlaces['error'] = 'Unable to geocode';
- }
-} else {
- if (isset($aPlace['place_id'])) {
- $aFilteredPlaces['place_id'] = $aPlace['place_id'];
- }
- $aFilteredPlaces['licence'] = 'Data © OpenStreetMap contributors, ODbL 1.0. https://osm.org/copyright';
- $sOSMType = formatOSMType($aPlace['osm_type']);
- if ($sOSMType) {
- $aFilteredPlaces['osm_type'] = $sOSMType;
- $aFilteredPlaces['osm_id'] = $aPlace['osm_id'];
- }
- if (isset($aPlace['lat'])) {
- $aFilteredPlaces['lat'] = $aPlace['lat'];
- }
- if (isset($aPlace['lon'])) {
- $aFilteredPlaces['lon'] = $aPlace['lon'];
- }
-
- if ($sOutputFormat == 'jsonv2' || $sOutputFormat == 'geojson') {
- $aFilteredPlaces['place_rank'] = $aPlace['rank_search'];
-
- $aFilteredPlaces['category'] = $aPlace['class'];
- $aFilteredPlaces['type'] = $aPlace['type'];
-
- $aFilteredPlaces['importance'] = $aPlace['importance'];
-
- $aFilteredPlaces['addresstype'] = strtolower($aPlace['addresstype']);
-
- $aFilteredPlaces['name'] = $aPlace['placename'];
- }
-
- $aFilteredPlaces['display_name'] = $aPlace['langaddress'];
-
- if (isset($aPlace['address'])) {
- $aFilteredPlaces['address'] = $aPlace['address']->getAddressNames();
- }
- if (isset($aPlace['sExtraTags'])) {
- $aFilteredPlaces['extratags'] = $aPlace['sExtraTags'];
- }
- if (isset($aPlace['sNameDetails'])) {
- $aFilteredPlaces['namedetails'] = $aPlace['sNameDetails'];
- }
-
- if (isset($aPlace['aBoundingBox'])) {
- $aFilteredPlaces['boundingbox'] = $aPlace['aBoundingBox'];
- }
-
- if (isset($aPlace['asgeojson'])) {
- $aFilteredPlaces['geojson'] = json_decode($aPlace['asgeojson'], true);
- }
-
- if (isset($aPlace['assvg'])) {
- $aFilteredPlaces['svg'] = $aPlace['assvg'];
- }
-
- if (isset($aPlace['astext'])) {
- $aFilteredPlaces['geotext'] = $aPlace['astext'];
- }
-
- if (isset($aPlace['askml'])) {
- $aFilteredPlaces['geokml'] = $aPlace['askml'];
- }
-}
-
-javascript_renderData($aFilteredPlaces);
+++ /dev/null
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-header('content-type: text/xml; charset=UTF-8');
-
-echo '<';
-echo '?xml version="1.0" encoding="UTF-8" ?';
-echo ">\n";
-
-echo '<reversegeocode';
-echo " timestamp='".date(DATE_RFC822)."'";
-echo " attribution='Data © OpenStreetMap contributors, ODbL 1.0. http://www.openstreetmap.org/copyright'";
-echo " querystring='".htmlspecialchars($_SERVER['QUERY_STRING'], ENT_QUOTES)."'";
-echo ">\n";
-
-if (empty($aPlace)) {
- if (isset($sError)) {
- echo "<error>$sError</error>";
- } else {
- echo '<error>Unable to geocode</error>';
- }
-} else {
- echo '<result';
- if ($aPlace['place_id']) {
- echo ' place_id="'.$aPlace['place_id'].'"';
- }
- $sOSMType = formatOSMType($aPlace['osm_type']);
- if ($sOSMType) {
- echo ' osm_type="'.$sOSMType.'"'.' osm_id="'.$aPlace['osm_id'].'"';
- }
- if ($aPlace['ref']) {
- echo ' ref="'.htmlspecialchars($aPlace['ref']).'"';
- }
- if (isset($aPlace['lat'])) {
- echo ' lat="'.htmlspecialchars($aPlace['lat']).'"';
- }
- if (isset($aPlace['lon'])) {
- echo ' lon="'.htmlspecialchars($aPlace['lon']).'"';
- }
- if (isset($aPlace['aBoundingBox'])) {
- echo ' boundingbox="';
- echo join(',', $aPlace['aBoundingBox']);
- echo '"';
- }
- echo " place_rank='".$aPlace['rank_search']."'";
- echo " address_rank='".$aPlace['rank_address']."'";
-
-
- if (isset($aPlace['asgeojson'])) {
- echo ' geojson=\'';
- echo $aPlace['asgeojson'];
- echo '\'';
- }
-
- if (isset($aPlace['assvg'])) {
- echo ' geosvg=\'';
- echo $aPlace['assvg'];
- echo '\'';
- }
-
- if (isset($aPlace['astext'])) {
- echo ' geotext=\'';
- echo $aPlace['astext'];
- echo '\'';
- }
- echo '>'.htmlspecialchars($aPlace['langaddress']).'</result>';
-
- if (isset($aPlace['address'])) {
- echo '<addressparts>';
- foreach ($aPlace['address']->getAddressNames() as $sKey => $sValue) {
- $sKey = str_replace(' ', '_', $sKey);
- echo "<$sKey>";
- echo htmlspecialchars($sValue);
- echo "</$sKey>";
- }
- echo '</addressparts>';
- }
-
- if (isset($aPlace['sExtraTags'])) {
- echo '<extratags>';
- foreach ($aPlace['sExtraTags'] as $sKey => $sValue) {
- echo '<tag key="'.htmlspecialchars($sKey).'" value="'.htmlspecialchars($sValue).'"/>';
- }
- echo '</extratags>';
- }
-
- if (isset($aPlace['sNameDetails'])) {
- echo '<namedetails>';
- foreach ($aPlace['sNameDetails'] as $sKey => $sValue) {
- echo '<name desc="'.htmlspecialchars($sKey).'">';
- echo htmlspecialchars($sValue);
- echo '</name>';
- }
- echo '</namedetails>';
- }
-
- if (isset($aPlace['askml'])) {
- echo "\n<geokml>";
- echo $aPlace['askml'];
- echo '</geokml>';
- }
-}
-
-echo '</reversegeocode>';
+++ /dev/null
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-$aPlaceDetails = array();
-
-$aPlaceDetails['place_id'] = (int) $aPointDetails['place_id'];
-$aPlaceDetails['parent_place_id'] = (int) $aPointDetails['parent_place_id'];
-
-$aPlaceDetails['osm_type'] = $aPointDetails['osm_type'];
-$aPlaceDetails['osm_id'] = (int) $aPointDetails['osm_id'];
-
-$aPlaceDetails['category'] = $aPointDetails['class'];
-$aPlaceDetails['type'] = $aPointDetails['type'];
-$aPlaceDetails['admin_level'] = $aPointDetails['admin_level'];
-
-$aPlaceDetails['localname'] = $aPointDetails['localname'];
-$aPlaceDetails['names'] = $aPointDetails['aNames'];
-
-$aPlaceDetails['addresstags'] = $aPointDetails['aAddressTags'];
-$aPlaceDetails['housenumber'] = $aPointDetails['housenumber'];
-$aPlaceDetails['calculated_postcode'] = $aPointDetails['postcode'];
-$aPlaceDetails['country_code'] = $aPointDetails['country_code'];
-
-$aPlaceDetails['indexed_date'] = (new DateTime('@'.$aPointDetails['indexed_epoch']))->format(DateTime::RFC3339);
-$aPlaceDetails['importance'] = (float) $aPointDetails['importance'];
-$aPlaceDetails['calculated_importance'] = (float) $aPointDetails['calculated_importance'];
-
-$aPlaceDetails['extratags'] = $aPointDetails['aExtraTags'];
-$aPlaceDetails['calculated_wikipedia'] = $aPointDetails['wikipedia'];
-$sIcon = Nominatim\ClassTypes\getIconFile($aPointDetails);
-if (isset($sIcon)) {
- $aPlaceDetails['icon'] = $sIcon;
-}
-
-$aPlaceDetails['rank_address'] = (int) $aPointDetails['rank_address'];
-$aPlaceDetails['rank_search'] = (int) $aPointDetails['rank_search'];
-
-$aPlaceDetails['isarea'] = $aPointDetails['isarea'];
-$aPlaceDetails['centroid'] = array(
- 'type' => 'Point',
- 'coordinates' => array( (float) $aPointDetails['lon'], (float) $aPointDetails['lat'] )
- );
-
-$aPlaceDetails['geometry'] = json_decode($aPointDetails['asgeojson'], true);
-
-$funcMapAddressLine = function ($aFull) {
- return array(
- 'localname' => $aFull['localname'],
- 'place_id' => isset($aFull['place_id']) ? (int) $aFull['place_id'] : null,
- 'osm_id' => isset($aFull['osm_id']) ? (int) $aFull['osm_id'] : null,
- 'osm_type' => isset($aFull['osm_type']) ? $aFull['osm_type'] : null,
- 'place_type' => isset($aFull['place_type']) ? $aFull['place_type'] : null,
- 'class' => $aFull['class'],
- 'type' => $aFull['type'],
- 'admin_level' => isset($aFull['admin_level']) ? (int) $aFull['admin_level'] : null,
- 'rank_address' => $aFull['rank_address'] ? (int) $aFull['rank_address'] : null,
- 'distance' => (float) $aFull['distance'],
- 'isaddress' => isset($aFull['isaddress']) ? (bool) $aFull['isaddress'] : null
- );
-};
-
-$funcMapKeyword = function ($aFull) {
- return array(
- 'id' => (int) $aFull['word_id'],
- 'token' => $aFull['word_token']
- );
-};
-
-if ($aAddressLines) {
- $aPlaceDetails['address'] = array_map($funcMapAddressLine, $aAddressLines);
-}
-
-if ($aLinkedLines) {
- $aPlaceDetails['linked_places'] = array_map($funcMapAddressLine, $aLinkedLines);
-}
-
-if ($bIncludeKeywords) {
- $aPlaceDetails['keywords'] = array();
-
- if ($aPlaceSearchNameKeywords) {
- $aPlaceDetails['keywords']['name'] = array_map($funcMapKeyword, $aPlaceSearchNameKeywords);
- } else {
- $aPlaceDetails['keywords']['name'] = array();
- }
-
- if ($aPlaceSearchAddressKeywords) {
- $aPlaceDetails['keywords']['address'] = array_map($funcMapKeyword, $aPlaceSearchAddressKeywords);
- } else {
- $aPlaceDetails['keywords']['address'] = array();
- }
-}
-
-if ($bIncludeHierarchy) {
- if ($bGroupHierarchy) {
- $aPlaceDetails['hierarchy'] = array();
- foreach ($aHierarchyLines as $aAddressLine) {
- if ($aAddressLine['type'] == 'yes') {
- $sType = $aAddressLine['class'];
- } else {
- $sType = $aAddressLine['type'];
- }
-
- if (!isset($aPlaceDetails['hierarchy'][$sType])) {
- $aPlaceDetails['hierarchy'][$sType] = array();
- }
- $aPlaceDetails['hierarchy'][$sType][] = $funcMapAddressLine($aAddressLine);
- }
- } else {
- $aPlaceDetails['hierarchy'] = array_map($funcMapAddressLine, $aHierarchyLines);
- }
-}
-
-javascript_renderData($aPlaceDetails);
+++ /dev/null
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
- $error = array(
- 'code' => $exception->getCode(),
- 'message' => $exception->getMessage()
- );
-
- if (CONST_Debug) {
- $error['details'] = $exception->getFile() . '('. $exception->getLine() . ')';
- }
-
- javascript_renderData(array('error' => $error));
+++ /dev/null
-<error>
- <code><?php echo $exception->getCode() ?></code>
- <message><?php echo $exception->getMessage() ?></message>
- <?php if (CONST_Debug) { ?>
- <details><?php echo $exception->getFile() . '('. $exception->getLine() . ')' ?></details>
- <?php } ?>
-</error>
\ No newline at end of file
+++ /dev/null
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-$aOutput = array();
-$aOutput['licence'] = 'Data © OpenStreetMap contributors, ODbL 1.0. https://osm.org/copyright';
-$aOutput['batch'] = array();
-
-foreach ($aBatchResults as $aSearchResults) {
- if (!$aSearchResults) {
- $aSearchResults = array();
- }
- $aFilteredPlaces = array();
- foreach ($aSearchResults as $iResNum => $aPointDetails) {
- $aPlace = array(
- 'place_id'=>$aPointDetails['place_id'],
- );
-
- $sOSMType = formatOSMType($aPointDetails['osm_type']);
- if ($sOSMType) {
- $aPlace['osm_type'] = $sOSMType;
- $aPlace['osm_id'] = $aPointDetails['osm_id'];
- }
-
- if (isset($aPointDetails['aBoundingBox'])) {
- $aPlace['boundingbox'] = array(
- $aPointDetails['aBoundingBox'][0],
- $aPointDetails['aBoundingBox'][1],
- $aPointDetails['aBoundingBox'][2],
- $aPointDetails['aBoundingBox'][3]
- );
- }
-
- if (isset($aPointDetails['zoom'])) {
- $aPlace['zoom'] = $aPointDetails['zoom'];
- }
-
- $aPlace['lat'] = $aPointDetails['lat'];
- $aPlace['lon'] = $aPointDetails['lon'];
- $aPlace['display_name'] = $aPointDetails['name'];
- $aPlace['place_rank'] = $aPointDetails['rank_search'];
-
- $aPlace['category'] = $aPointDetails['class'];
- $aPlace['type'] = $aPointDetails['type'];
-
- $aPlace['importance'] = $aPointDetails['importance'];
-
- if (isset($aPointDetails['icon'])) {
- $aPlace['icon'] = $aPointDetails['icon'];
- }
-
- if (isset($aPointDetails['address'])) {
- $aPlace['address'] = $aPointDetails['address']->getAddressNames();
- }
-
- if (isset($aPointDetails['asgeojson'])) {
- $aPlace['geojson'] = json_decode($aPointDetails['asgeojson'], true);
- }
-
- if (isset($aPointDetails['assvg'])) {
- $aPlace['svg'] = $aPointDetails['assvg'];
- }
-
- if (isset($aPointDetails['astext'])) {
- $aPlace['geotext'] = $aPointDetails['astext'];
- }
-
- if (isset($aPointDetails['askml'])) {
- $aPlace['geokml'] = $aPointDetails['askml'];
- }
-
- $aFilteredPlaces[] = $aPlace;
- }
- $aOutput['batch'][] = $aFilteredPlaces;
-}
-
-javascript_renderData($aOutput, array('geojson'));
+++ /dev/null
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-$aFilteredPlaces = array();
-foreach ($aSearchResults as $iResNum => $aPointDetails) {
- $aPlace = array(
- 'type' => 'Feature',
- 'properties' => array(
- 'geocoding' => array()
- )
- );
-
- if (isset($aPointDetails['place_id'])) {
- $aPlace['properties']['geocoding']['place_id'] = $aPointDetails['place_id'];
- }
- $sOSMType = formatOSMType($aPointDetails['osm_type']);
- if ($sOSMType) {
- $aPlace['properties']['geocoding']['osm_type'] = $sOSMType;
- $aPlace['properties']['geocoding']['osm_id'] = $aPointDetails['osm_id'];
- }
- $aPlace['properties']['geocoding']['osm_key'] = $aPointDetails['class'];
- $aPlace['properties']['geocoding']['osm_value'] = $aPointDetails['type'];
-
- $aPlace['properties']['geocoding']['type'] = addressRankToGeocodeJsonType($aPointDetails['rank_address']);
-
- $aPlace['properties']['geocoding']['label'] = $aPointDetails['langaddress'];
-
- if ($aPointDetails['placename'] !== null) {
- $aPlace['properties']['geocoding']['name'] = $aPointDetails['placename'];
- }
-
- if (isset($aPointDetails['address'])) {
- $aPointDetails['address']->addGeocodeJsonAddressParts(
- $aPlace['properties']['geocoding']
- );
-
- $aPlace['properties']['geocoding']['admin']
- = $aPointDetails['address']->getAdminLevels();
- }
-
- if (isset($aPointDetails['asgeojson'])) {
- $aPlace['geometry'] = json_decode($aPointDetails['asgeojson'], true);
- } else {
- $aPlace['geometry'] = array(
- 'type' => 'Point',
- 'coordinates' => array(
- (float) $aPointDetails['lon'],
- (float) $aPointDetails['lat']
- )
- );
- }
- $aFilteredPlaces[] = $aPlace;
-}
-
-
-javascript_renderData(array(
- 'type' => 'FeatureCollection',
- 'geocoding' => array(
- 'version' => '0.1.0',
- 'attribution' => 'Data © OpenStreetMap contributors, ODbL 1.0. https://osm.org/copyright',
- 'licence' => 'ODbL',
- 'query' => $sQuery
- ),
- 'features' => $aFilteredPlaces
- ));
+++ /dev/null
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-$aFilteredPlaces = array();
-foreach ($aSearchResults as $iResNum => $aPointDetails) {
- $aPlace = array(
- 'type' => 'Feature',
- 'properties' => array(
- 'place_id'=>$aPointDetails['place_id'],
- )
- );
-
- $sOSMType = formatOSMType($aPointDetails['osm_type']);
- if ($sOSMType) {
- $aPlace['properties']['osm_type'] = $sOSMType;
- $aPlace['properties']['osm_id'] = $aPointDetails['osm_id'];
- }
-
- if (isset($aPointDetails['aBoundingBox'])) {
- $aPlace['bbox'] = array(
- (float) $aPointDetails['aBoundingBox'][2], // minlon
- (float) $aPointDetails['aBoundingBox'][0], // minlat
- (float) $aPointDetails['aBoundingBox'][3], // maxlon
- (float) $aPointDetails['aBoundingBox'][1] // maxlat
- );
- }
-
- if (isset($aPointDetails['zoom'])) {
- $aPlace['properties']['zoom'] = $aPointDetails['zoom'];
- }
-
- $aPlace['properties']['display_name'] = $aPointDetails['name'];
-
- $aPlace['properties']['place_rank'] = $aPointDetails['rank_search'];
- $aPlace['properties']['category'] = $aPointDetails['class'];
-
- $aPlace['properties']['type'] = $aPointDetails['type'];
-
- $aPlace['properties']['importance'] = $aPointDetails['importance'];
-
- if (isset($aPointDetails['icon']) && $aPointDetails['icon']) {
- $aPlace['properties']['icon'] = $aPointDetails['icon'];
- }
-
- if (isset($aPointDetails['address'])) {
- $aPlace['properties']['address'] = $aPointDetails['address']->getAddressNames();
- }
-
- if (isset($aPointDetails['asgeojson'])) {
- $aPlace['geometry'] = json_decode($aPointDetails['asgeojson'], true);
- } else {
- $aPlace['geometry'] = array(
- 'type' => 'Point',
- 'coordinates' => array(
- (float) $aPointDetails['lon'],
- (float) $aPointDetails['lat']
- )
- );
- }
-
-
- if (isset($aPointDetails['sExtraTags'])) {
- $aPlace['properties']['extratags'] = $aPointDetails['sExtraTags'];
- }
- if (isset($aPointDetails['sNameDetails'])) {
- $aPlace['properties']['namedetails'] = $aPointDetails['sNameDetails'];
- }
-
- $aFilteredPlaces[] = $aPlace;
-}
-
-javascript_renderData(array(
- 'type' => 'FeatureCollection',
- 'licence' => 'Data © OpenStreetMap contributors, ODbL 1.0. https://osm.org/copyright',
- 'features' => $aFilteredPlaces
- ));
+++ /dev/null
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-$aFilteredPlaces = array();
-foreach ($aSearchResults as $iResNum => $aPointDetails) {
- $aPlace = array(
- 'place_id'=>$aPointDetails['place_id'],
- 'licence'=>'Data © OpenStreetMap contributors, ODbL 1.0. https://osm.org/copyright',
- );
-
- $sOSMType = formatOSMType($aPointDetails['osm_type']);
- if ($sOSMType) {
- $aPlace['osm_type'] = $sOSMType;
- $aPlace['osm_id'] = $aPointDetails['osm_id'];
- }
-
- if (isset($aPointDetails['aBoundingBox'])) {
- $aPlace['boundingbox'] = $aPointDetails['aBoundingBox'];
- }
-
- if (isset($aPointDetails['zoom'])) {
- $aPlace['zoom'] = $aPointDetails['zoom'];
- }
-
- $aPlace['lat'] = $aPointDetails['lat'];
- $aPlace['lon'] = $aPointDetails['lon'];
-
- $aPlace['display_name'] = $aPointDetails['name'];
-
- if ($sOutputFormat == 'jsonv2' || $sOutputFormat == 'geojson') {
- $aPlace['place_rank'] = $aPointDetails['rank_search'];
- $aPlace['category'] = $aPointDetails['class'];
- } else {
- $aPlace['class'] = $aPointDetails['class'];
- }
- $aPlace['type'] = $aPointDetails['type'];
-
- $aPlace['importance'] = $aPointDetails['importance'];
-
- if (isset($aPointDetails['icon']) && $aPointDetails['icon']) {
- $aPlace['icon'] = $aPointDetails['icon'];
- }
-
- if (isset($aPointDetails['address'])) {
- $aPlace['address'] = $aPointDetails['address']->getAddressNames();
- }
-
- if (isset($aPointDetails['asgeojson'])) {
- $aPlace['geojson'] = json_decode($aPointDetails['asgeojson'], true);
- }
-
- if (isset($aPointDetails['assvg'])) {
- $aPlace['svg'] = $aPointDetails['assvg'];
- }
-
- if (isset($aPointDetails['astext'])) {
- $aPlace['geotext'] = $aPointDetails['astext'];
- }
-
- if (isset($aPointDetails['askml'])) {
- $aPlace['geokml'] = $aPointDetails['askml'];
- }
-
- if (isset($aPointDetails['sExtraTags'])) {
- $aPlace['extratags'] = $aPointDetails['sExtraTags'];
- }
- if (isset($aPointDetails['sNameDetails'])) {
- $aPlace['namedetails'] = $aPointDetails['sNameDetails'];
- }
-
- $aFilteredPlaces[] = $aPlace;
-}
-
-javascript_renderData($aFilteredPlaces);
+++ /dev/null
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-header('content-type: text/xml; charset=UTF-8');
-
-echo '<';
-echo '?xml version="1.0" encoding="UTF-8" ?';
-echo ">\n";
-
-echo '<';
-echo (isset($sXmlRootTag)?$sXmlRootTag:'searchresults');
-echo " timestamp='".date(DATE_RFC822)."'";
-echo " attribution='Data © OpenStreetMap contributors, ODbL 1.0. http://www.openstreetmap.org/copyright'";
-echo " querystring='".htmlspecialchars($sQuery, ENT_QUOTES)."'";
-if (isset($aMoreParams['viewbox'])) {
- echo " viewbox='".htmlspecialchars($aMoreParams['viewbox'], ENT_QUOTES)."'";
-}
-if (isset($aMoreParams['exclude_place_ids'])) {
- echo " exclude_place_ids='".htmlspecialchars($aMoreParams['exclude_place_ids'])."'";
-}
-echo " more_url='".htmlspecialchars($sMoreURL)."'";
-echo ">\n";
-
-foreach ($aSearchResults as $iResNum => $aResult) {
- echo "<place place_id='".$aResult['place_id']."'";
- $sOSMType = formatOSMType($aResult['osm_type']);
- if ($sOSMType) {
- echo " osm_type='$sOSMType'";
- echo " osm_id='".$aResult['osm_id']."'";
- }
- echo " place_rank='".$aResult['rank_search']."'";
- echo " address_rank='".$aResult['rank_address']."'";
-
- if (isset($aResult['aBoundingBox'])) {
- echo ' boundingbox="';
- echo join(',', $aResult['aBoundingBox']);
- echo '"';
- }
-
- if (isset($aResult['asgeojson'])) {
- echo ' geojson=\'';
- echo $aResult['asgeojson'];
- echo '\'';
- }
-
- if (isset($aResult['assvg'])) {
- echo ' geosvg=\'';
- echo $aResult['assvg'];
- echo '\'';
- }
-
- if (isset($aResult['astext'])) {
- echo ' geotext=\'';
- echo $aResult['astext'];
- echo '\'';
- }
-
- if (isset($aResult['zoom'])) {
- echo " zoom='".$aResult['zoom']."'";
- }
-
- echo " lat='".$aResult['lat']."'";
- echo " lon='".$aResult['lon']."'";
- echo " display_name='".htmlspecialchars($aResult['name'], ENT_QUOTES)."'";
-
- echo " class='".htmlspecialchars($aResult['class'])."'";
- echo " type='".htmlspecialchars($aResult['type'], ENT_QUOTES)."'";
- echo " importance='".htmlspecialchars($aResult['importance'])."'";
- if (isset($aResult['icon']) && $aResult['icon']) {
- echo " icon='".htmlspecialchars($aResult['icon'], ENT_QUOTES)."'";
- }
-
- $bHasDelim = false;
-
- if (isset($aResult['askml'])) {
- if (!$bHasDelim) {
- $bHasDelim = true;
- echo '>';
- }
- echo "\n<geokml>";
- echo $aResult['askml'];
- echo '</geokml>';
- }
-
- if (isset($aResult['sExtraTags'])) {
- if (!$bHasDelim) {
- $bHasDelim = true;
- echo '>';
- }
- echo "\n<extratags>";
- foreach ($aResult['sExtraTags'] as $sKey => $sValue) {
- echo '<tag key="'.htmlspecialchars($sKey).'" value="'.htmlspecialchars($sValue).'"/>';
- }
- echo '</extratags>';
- }
-
- if (isset($aResult['sNameDetails'])) {
- if (!$bHasDelim) {
- $bHasDelim = true;
- echo '>';
- }
- echo "\n<namedetails>";
- foreach ($aResult['sNameDetails'] as $sKey => $sValue) {
- echo '<name desc="'.htmlspecialchars($sKey).'">';
- echo htmlspecialchars($sValue);
- echo '</name>';
- }
- echo '</namedetails>';
- }
-
- if (isset($aResult['address'])) {
- if (!$bHasDelim) {
- $bHasDelim = true;
- echo '>';
- }
- echo "\n";
- foreach ($aResult['address']->getAddressNames() as $sKey => $sValue) {
- $sKey = str_replace(' ', '_', $sKey);
- echo "<$sKey>";
- echo htmlspecialchars($sValue);
- echo "</$sKey>";
- }
- }
-
- if ($bHasDelim) {
- echo '</place>';
- } else {
- echo '/>';
- }
-}
-
-echo '</' . (isset($sXmlRootTag)?$sXmlRootTag:'searchresults') . '>';
+++ /dev/null
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-namespace Nominatim;
-
-require_once(CONST_LibDir.'/SimpleWordList.php');
-
-class Tokenizer
-{
- private $oDB;
-
- private $oNormalizer;
- private $oTransliterator;
-
- public function __construct(&$oDB)
- {
- $this->oDB =& $oDB;
- $this->oNormalizer = \Transliterator::createFromRules(CONST_Term_Normalization_Rules);
- $this->oTransliterator = \Transliterator::createFromRules(CONST_Transliteration);
- }
-
- public function checkStatus()
- {
- $sSQL = 'SELECT word_id FROM word WHERE word_id is not null limit 1';
- $iWordID = $this->oDB->getOne($sSQL);
- if ($iWordID === false) {
- throw new \Exception('Query failed', 703);
- }
- if (!$iWordID) {
- throw new \Exception('No value', 704);
- }
- }
-
-
- public function normalizeString($sTerm)
- {
- if ($this->oNormalizer === null) {
- return $sTerm;
- }
-
- return $this->oNormalizer->transliterate($sTerm);
- }
-
-
- public function mostFrequentWords($iNum)
- {
- $sSQL = "SELECT word FROM word WHERE type = 'W'";
- $sSQL .= "ORDER BY info->'count' DESC LIMIT ".$iNum;
- return $this->oDB->getCol($sSQL);
- }
-
-
- private function makeStandardWord($sTerm)
- {
- return trim($this->oTransliterator->transliterate(' '.$sTerm.' '));
- }
-
-
- public function tokensForSpecialTerm($sTerm)
- {
- $aResults = array();
-
- $sSQL = "SELECT word_id, info->>'class' as class, info->>'type' as type ";
- $sSQL .= ' FROM word WHERE word_token = :term and type = \'S\'';
-
- Debug::printVar('Term', $sTerm);
- Debug::printSQL($sSQL);
- $aSearchWords = $this->oDB->getAll($sSQL, array(':term' => $this->makeStandardWord($sTerm)));
-
- Debug::printVar('Results', $aSearchWords);
-
- foreach ($aSearchWords as $aSearchTerm) {
- $aResults[] = new \Nominatim\Token\SpecialTerm(
- $aSearchTerm['word_id'],
- $aSearchTerm['class'],
- $aSearchTerm['type'],
- \Nominatim\Operator::TYPE
- );
- }
-
- Debug::printVar('Special term tokens', $aResults);
-
- return $aResults;
- }
-
-
- public function extractTokensFromPhrases(&$aPhrases)
- {
- $sNormQuery = '';
- $aWordLists = array();
- $aTokens = array();
- foreach ($aPhrases as $iPhrase => $oPhrase) {
- $sNormQuery .= ','.$this->normalizeString($oPhrase->getPhrase());
- $sPhrase = $this->makeStandardWord($oPhrase->getPhrase());
- Debug::printVar('Phrase', $sPhrase);
-
- $oWordList = new SimpleWordList($sPhrase);
- $aTokens = array_merge($aTokens, $oWordList->getTokens());
- $aWordLists[] = $oWordList;
- }
-
- Debug::printVar('Tokens', $aTokens);
- Debug::printVar('WordLists', $aWordLists);
-
- $oValidTokens = $this->computeValidTokens($aTokens, $sNormQuery);
-
- foreach ($aPhrases as $iPhrase => $oPhrase) {
- $oPhrase->setWordSets($aWordLists[$iPhrase]->getWordSets($oValidTokens));
- }
-
- return $oValidTokens;
- }
-
-
- private function computeValidTokens($aTokens, $sNormQuery)
- {
- $oValidTokens = new TokenList();
-
- if (!empty($aTokens)) {
- $this->addTokensFromDB($oValidTokens, $aTokens, $sNormQuery);
-
- // Try more interpretations for Tokens that could not be matched.
- foreach ($aTokens as $sToken) {
- if ($sToken[0] != ' ' && !$oValidTokens->contains($sToken)) {
- if (preg_match('/^([0-9]{5}) [0-9]{4}$/', $sToken, $aData)) {
- // US ZIP+4 codes - merge in the 5-digit ZIP code
- $oValidTokens->addToken(
- $sToken,
- new Token\Postcode(null, $aData[1], 'us')
- );
- } elseif (preg_match('/^[0-9]+$/', $sToken)) {
- // Unknown single word token with a number.
- // Assume it is a house number.
- $oValidTokens->addToken(
- $sToken,
- new Token\HouseNumber(null, trim($sToken))
- );
- }
- }
- }
- }
-
- return $oValidTokens;
- }
-
-
- private function addTokensFromDB(&$oValidTokens, $aTokens, $sNormQuery)
- {
- // Check which tokens we have, get the ID numbers
- $sSQL = 'SELECT word_id, word_token, type, word,';
- $sSQL .= " info->>'op' as operator,";
- $sSQL .= " info->>'class' as class, info->>'type' as ctype,";
- $sSQL .= " info->>'count' as count,";
- $sSQL .= " info->>'lookup' as lookup";
- $sSQL .= ' FROM word WHERE word_token in (';
- $sSQL .= join(',', $this->oDB->getDBQuotedList($aTokens)).')';
-
- Debug::printSQL($sSQL);
-
- $aDBWords = $this->oDB->getAll($sSQL, null, 'Could not get word tokens.');
-
- foreach ($aDBWords as $aWord) {
- $iId = (int) $aWord['word_id'];
- $sTok = $aWord['word_token'];
-
- switch ($aWord['type']) {
- case 'C': // country name tokens
- if ($aWord['word'] !== null) {
- $oValidTokens->addToken(
- $sTok,
- new Token\Country($iId, $aWord['word'])
- );
- }
- break;
- case 'H': // house number tokens
- $sLookup = $aWord['lookup'] ?? $aWord['word_token'];
- $oValidTokens->addToken($sTok, new Token\HouseNumber($iId, $sLookup));
- break;
- case 'P': // postcode tokens
- // Postcodes are not normalized, so they may have content
- // that makes SQL injection possible. Reject postcodes
- // that would need special escaping.
- if ($aWord['word'] !== null
- && pg_escape_string($aWord['word']) == $aWord['word']
- ) {
- $iSplitPos = strpos($aWord['word'], '@');
- if ($iSplitPos === false) {
- $sPostcode = $aWord['word'];
- } else {
- $sPostcode = substr($aWord['word'], 0, $iSplitPos);
- }
-
- $oValidTokens->addToken(
- $sTok,
- new Token\Postcode($iId, $sPostcode, null)
- );
- }
- break;
- case 'S': // tokens for classification terms (special phrases)
- if ($aWord['class'] !== null && $aWord['ctype'] !== null) {
- $oValidTokens->addToken($sTok, new Token\SpecialTerm(
- $iId,
- $aWord['class'],
- $aWord['ctype'],
- (isset($aWord['operator'])) ? Operator::NEAR : Operator::NONE
- ));
- }
- break;
- case 'W': // full-word tokens
- $oValidTokens->addToken($sTok, new Token\Word(
- $iId,
- (int) $aWord['count'],
- substr_count($aWord['word_token'], ' ')
- ));
- break;
- case 'w': // partial word terms
- $oValidTokens->addToken($sTok, new Token\Partial(
- $iId,
- $aWord['word_token'],
- (int) $aWord['count']
- ));
- break;
- default:
- break;
- }
- }
- }
-}
+++ /dev/null
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-namespace Nominatim;
-
-require_once(CONST_LibDir.'/SimpleWordList.php');
-
-class Tokenizer
-{
- private $oDB;
-
- private $oNormalizer = null;
-
- public function __construct(&$oDB)
- {
- $this->oDB =& $oDB;
- $this->oNormalizer = \Transliterator::createFromRules(CONST_Term_Normalization_Rules);
- }
-
- public function checkStatus()
- {
- $sStandardWord = $this->oDB->getOne("SELECT make_standard_name('a')");
- if ($sStandardWord === false) {
- throw new \Exception('Module failed', 701);
- }
-
- if ($sStandardWord != 'a') {
- throw new \Exception('Module call failed', 702);
- }
-
- $sSQL = "SELECT word_id FROM word WHERE word_token IN (' a')";
- $iWordID = $this->oDB->getOne($sSQL);
- if ($iWordID === false) {
- throw new \Exception('Query failed', 703);
- }
- if (!$iWordID) {
- throw new \Exception('No value', 704);
- }
- }
-
-
- public function normalizeString($sTerm)
- {
- if ($this->oNormalizer === null) {
- return $sTerm;
- }
-
- return $this->oNormalizer->transliterate($sTerm);
- }
-
-
- public function mostFrequentWords($iNum)
- {
- $sSQL = 'SELECT word FROM word WHERE word is not null ';
- $sSQL .= 'ORDER BY search_name_count DESC LIMIT '.$iNum;
- return $this->oDB->getCol($sSQL);
- }
-
-
- public function tokensForSpecialTerm($sTerm)
- {
- $aResults = array();
-
- $sSQL = 'SELECT word_id, class, type FROM word ';
- $sSQL .= ' WHERE word_token = \' \' || make_standard_name(:term)';
- $sSQL .= ' AND class is not null AND class not in (\'place\')';
-
- Debug::printVar('Term', $sTerm);
- Debug::printSQL($sSQL);
- $aSearchWords = $this->oDB->getAll($sSQL, array(':term' => $sTerm));
-
- Debug::printVar('Results', $aSearchWords);
-
- foreach ($aSearchWords as $aSearchTerm) {
- $aResults[] = new \Nominatim\Token\SpecialTerm(
- $aSearchTerm['word_id'],
- $aSearchTerm['class'],
- $aSearchTerm['type'],
- \Nominatim\Operator::TYPE
- );
- }
-
- Debug::printVar('Special term tokens', $aResults);
-
- return $aResults;
- }
-
-
- public function extractTokensFromPhrases(&$aPhrases)
- {
- // First get the normalized version of all phrases
- $sNormQuery = '';
- $sSQL = 'SELECT ';
- $aParams = array();
- foreach ($aPhrases as $iPhrase => $oPhrase) {
- $sNormQuery .= ','.$this->normalizeString($oPhrase->getPhrase());
- $sSQL .= 'make_standard_name(:' .$iPhrase.') as p'.$iPhrase.',';
- $aParams[':'.$iPhrase] = $oPhrase->getPhrase();
-
- // Conflicts between US state abbreviations and various words
- // for 'the' in different languages
- switch (strtolower($oPhrase->getPhrase())) {
- case 'il':
- $aParams[':'.$iPhrase] = 'illinois';
- break;
- case 'al':
- $aParams[':'.$iPhrase] = 'alabama';
- break;
- case 'la':
- $aParams[':'.$iPhrase] = 'louisiana';
- break;
- default:
- $aParams[':'.$iPhrase] = $oPhrase->getPhrase();
- break;
- }
- }
- $sSQL = substr($sSQL, 0, -1);
-
- Debug::printSQL($sSQL);
- Debug::printVar('SQL parameters', $aParams);
-
- $aNormPhrases = $this->oDB->getRow($sSQL, $aParams);
-
- Debug::printVar('SQL result', $aNormPhrases);
-
- // now compute all possible tokens
- $aWordLists = array();
- $aTokens = array();
- foreach ($aNormPhrases as $sPhrase) {
- $oWordList = new SimpleWordList($sPhrase);
-
- foreach ($oWordList->getTokens() as $sToken) {
- $aTokens[' '.$sToken] = ' '.$sToken;
- $aTokens[$sToken] = $sToken;
- }
-
- $aWordLists[] = $oWordList;
- }
-
- Debug::printVar('Tokens', $aTokens);
- Debug::printVar('WordLists', $aWordLists);
-
- $oValidTokens = $this->computeValidTokens($aTokens, $sNormQuery);
-
- foreach ($aPhrases as $iPhrase => $oPhrase) {
- $oPhrase->setWordSets($aWordLists[$iPhrase]->getWordSets($oValidTokens));
- }
-
- return $oValidTokens;
- }
-
-
- private function computeValidTokens($aTokens, $sNormQuery)
- {
- $oValidTokens = new TokenList();
-
- if (!empty($aTokens)) {
- $this->addTokensFromDB($oValidTokens, $aTokens, $sNormQuery);
-
- // Try more interpretations for Tokens that could not be matched.
- foreach ($aTokens as $sToken) {
- if ($sToken[0] != ' ' && !$oValidTokens->contains($sToken)) {
- if (preg_match('/^([0-9]{5}) [0-9]{4}$/', $sToken, $aData)) {
- // US ZIP+4 codes - merge in the 5-digit ZIP code
- $oValidTokens->addToken(
- $sToken,
- new Token\Postcode(null, $aData[1], 'us')
- );
- } elseif (preg_match('/^[0-9]+$/', $sToken)) {
- // Unknown single word token with a number.
- // Assume it is a house number.
- $oValidTokens->addToken(
- $sToken,
- new Token\HouseNumber(null, trim($sToken))
- );
- }
- }
- }
- }
-
- return $oValidTokens;
- }
-
-
- private function addTokensFromDB(&$oValidTokens, $aTokens, $sNormQuery)
- {
- // Check which tokens we have, get the ID numbers
- $sSQL = 'SELECT word_id, word_token, word, class, type, country_code,';
- $sSQL .= ' operator, coalesce(search_name_count, 0) as count';
- $sSQL .= ' FROM word WHERE word_token in (';
- $sSQL .= join(',', $this->oDB->getDBQuotedList($aTokens)).')';
-
- Debug::printSQL($sSQL);
-
- $aDBWords = $this->oDB->getAll($sSQL, null, 'Could not get word tokens.');
-
- foreach ($aDBWords as $aWord) {
- $oToken = null;
- $iId = (int) $aWord['word_id'];
-
- if ($aWord['class']) {
- // Special terms need to appear in their normalized form.
- // (postcodes are not normalized in the word table)
- $sNormWord = $this->normalizeString($aWord['word']);
- if ($aWord['word'] && strpos($sNormQuery, $sNormWord) === false) {
- continue;
- }
-
- if ($aWord['class'] == 'place' && $aWord['type'] == 'house') {
- $oToken = new Token\HouseNumber($iId, trim($aWord['word_token']));
- } elseif ($aWord['class'] == 'place' && $aWord['type'] == 'postcode') {
- if ($aWord['word']
- && pg_escape_string($aWord['word']) == $aWord['word']
- ) {
- $oToken = new Token\Postcode(
- $iId,
- $aWord['word'],
- $aWord['country_code']
- );
- }
- } else {
- // near and in operator the same at the moment
- $oToken = new Token\SpecialTerm(
- $iId,
- $aWord['class'],
- $aWord['type'],
- $aWord['operator'] ? Operator::NEAR : Operator::NONE
- );
- }
- } elseif ($aWord['country_code']) {
- $oToken = new Token\Country($iId, $aWord['country_code']);
- } elseif ($aWord['word_token'][0] == ' ') {
- $oToken = new Token\Word(
- $iId,
- (int) $aWord['count'],
- substr_count($aWord['word_token'], ' ')
- );
- // For backward compatibility: ignore all partial tokens with more
- // than one word.
- } elseif (strpos($aWord['word_token'], ' ') === false) {
- $oToken = new Token\Partial(
- $iId,
- $aWord['word_token'],
- (int) $aWord['count']
- );
- }
-
- if ($oToken) {
- // remove any leading spaces
- if ($aWord['word_token'][0] == ' ') {
- $oValidTokens->addToken(substr($aWord['word_token'], 1), $oToken);
- } else {
- $oValidTokens->addToken($aWord['word_token'], $oToken);
- }
- }
- }
- }
-}
+++ /dev/null
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-require_once(CONST_LibDir.'/init-website.php');
-require_once(CONST_LibDir.'/log.php');
-require_once(CONST_LibDir.'/output.php');
-ini_set('memory_limit', '200M');
-
-$oParams = new Nominatim\ParameterParser();
-$sOutputFormat = $oParams->getSet('format', array('json'), 'json');
-set_exception_handler_by_format($sOutputFormat);
-
-$oDB = new Nominatim\DB(CONST_Database_DSN);
-$oDB->connect();
-
-$sSQL = 'select placex.place_id, country_code,';
-$sSQL .= " name->'name' as name, i.* from placex, import_polygon_delete i";
-$sSQL .= ' where placex.osm_id = i.osm_id and placex.osm_type = i.osm_type';
-$sSQL .= ' and placex.class = i.class and placex.type = i.type';
-$aPolygons = $oDB->getAll($sSQL, null, 'Could not get list of deleted OSM elements.');
-
-if (CONST_Debug) {
- var_dump($aPolygons);
- exit;
-}
-
-if ($sOutputFormat == 'json') {
- javascript_renderData($aPolygons);
-}
+++ /dev/null
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-require_once(CONST_LibDir.'/init-website.php');
-require_once(CONST_LibDir.'/log.php');
-require_once(CONST_LibDir.'/output.php');
-require_once(CONST_LibDir.'/AddressDetails.php');
-ini_set('memory_limit', '200M');
-
-$oParams = new Nominatim\ParameterParser();
-
-$sOutputFormat = $oParams->getSet('format', array('json'), 'json');
-set_exception_handler_by_format($sOutputFormat);
-
-$aLangPrefOrder = $oParams->getPreferredLanguages();
-
-$sPlaceId = $oParams->getString('place_id');
-$sOsmType = $oParams->getSet('osmtype', array('N', 'W', 'R'));
-$iOsmId = $oParams->getInt('osmid', 0);
-$sClass = $oParams->getString('class');
-
-$bIncludeKeywords = $oParams->getBool('keywords', false);
-$bIncludeAddressDetails = $oParams->getBool('addressdetails', false);
-$bIncludeLinkedPlaces = $oParams->getBool('linkedplaces', true);
-$bIncludeHierarchy = $oParams->getBool('hierarchy', false);
-$bGroupHierarchy = $oParams->getBool('group_hierarchy', false);
-$bIncludePolygonAsGeoJSON = $oParams->getBool('polygon_geojson', false);
-
-$oDB = new Nominatim\DB(CONST_Database_DSN);
-$oDB->connect();
-
-$sLanguagePrefArraySQL = $oDB->getArraySQL($oDB->getDBQuotedList($aLangPrefOrder));
-
-if ($sOsmType && $iOsmId !== 0) {
- $sSQL = 'SELECT place_id FROM placex WHERE osm_type = :type AND osm_id = :id';
- $aSQLParams = array(':type' => $sOsmType, ':id' => $iOsmId);
- // osm_type and osm_id are not unique enough
- if ($sClass) {
- $sSQL .= ' AND class= :class';
- $aSQLParams[':class'] = $sClass;
- }
- $sSQL .= ' ORDER BY class ASC';
- $sPlaceId = $oDB->getOne($sSQL, $aSQLParams);
-
-
- // Nothing? Maybe it's an interpolation.
- // XXX Simply returns the first parent street it finds. It should
- // get a house number and get the right interpolation.
- if (!$sPlaceId && $sOsmType == 'W' && (!$sClass || $sClass == 'place')) {
- $sSQL = 'SELECT place_id FROM location_property_osmline'
- .' WHERE osm_id = :id LIMIT 1';
- $sPlaceId = $oDB->getOne($sSQL, array(':id' => $iOsmId));
- }
-
- // Be nice about our error messages for broken geometry
-
- if (!$sPlaceId && $oDB->tableExists('import_polygon_error')) {
- $sSQL = 'SELECT ';
- $sSQL .= ' osm_type, ';
- $sSQL .= ' osm_id, ';
- $sSQL .= ' errormessage, ';
- $sSQL .= ' class, ';
- $sSQL .= ' type, ';
- $sSQL .= " get_name_by_language(name,$sLanguagePrefArraySQL) AS localname,";
- $sSQL .= ' ST_AsText(prevgeometry) AS prevgeom, ';
- $sSQL .= ' ST_AsText(newgeometry) AS newgeom';
- $sSQL .= ' FROM import_polygon_error ';
- $sSQL .= ' WHERE osm_type = :type';
- $sSQL .= ' AND osm_id = :id';
- $sSQL .= ' ORDER BY updated DESC';
- $sSQL .= ' LIMIT 1';
- $aPointDetails = $oDB->getRow($sSQL, array(':type' => $sOsmType, ':id' => $iOsmId));
- if ($aPointDetails) {
- if (preg_match('/\[(-?\d+\.\d+) (-?\d+\.\d+)\]/', $aPointDetails['errormessage'], $aMatches)) {
- $aPointDetails['error_x'] = $aMatches[1];
- $aPointDetails['error_y'] = $aMatches[2];
- } else {
- $aPointDetails['error_x'] = 0;
- $aPointDetails['error_y'] = 0;
- }
- include(CONST_LibDir.'/template/details-error-'.$sOutputFormat.'.php');
- exit;
- }
- }
-
- if ($sPlaceId === false) {
- throw new \Exception('No place with that OSM ID found.', 404);
- }
-} else {
- if ($sPlaceId === false) {
- userError('Required parameters missing. Need either osmtype/osmid or place_id.');
- }
-}
-
-$iPlaceID = (int)$sPlaceId;
-
-if (CONST_Use_US_Tiger_Data) {
- $iParentPlaceID = $oDB->getOne('SELECT parent_place_id FROM location_property_tiger WHERE place_id = '.$iPlaceID);
- if ($iParentPlaceID) {
- $iPlaceID = $iParentPlaceID;
- }
-}
-
-// interpolated house numbers
-$iParentPlaceID = $oDB->getOne('SELECT parent_place_id FROM location_property_osmline WHERE place_id = '.$iPlaceID);
-if ($iParentPlaceID) {
- $iPlaceID = $iParentPlaceID;
-}
-
-// artificial postcodes
-$iParentPlaceID = $oDB->getOne('SELECT parent_place_id FROM location_postcode WHERE place_id = '.$iPlaceID);
-if ($iParentPlaceID) {
- $iPlaceID = $iParentPlaceID;
-}
-
-$hLog = logStart($oDB, 'details', $_SERVER['QUERY_STRING'], $aLangPrefOrder);
-
-// Get the details for this point
-$sSQL = 'SELECT place_id, osm_type, osm_id, class, type, name, admin_level,';
-$sSQL .= ' housenumber, postcode, country_code,';
-$sSQL .= ' importance, wikipedia,';
-$sSQL .= ' ROUND(EXTRACT(epoch FROM indexed_date)) AS indexed_epoch,';
-$sSQL .= ' parent_place_id, ';
-$sSQL .= ' rank_address, ';
-$sSQL .= ' rank_search, ';
-$sSQL .= " get_name_by_language(name,$sLanguagePrefArraySQL) AS localname, ";
-$sSQL .= " ST_GeometryType(geometry) in ('ST_Polygon','ST_MultiPolygon') AS isarea, ";
-$sSQL .= ' ST_y(centroid) AS lat, ';
-$sSQL .= ' ST_x(centroid) AS lon, ';
-$sSQL .= ' CASE ';
-$sSQL .= ' WHEN importance = 0 OR importance IS NULL ';
-$sSQL .= ' THEN 0.75-(rank_search::float/40) ';
-$sSQL .= ' ELSE importance ';
-$sSQL .= ' END as calculated_importance, ';
-if ($bIncludePolygonAsGeoJSON) {
- $sSQL .= ' ST_AsGeoJSON(CASE ';
- $sSQL .= ' WHEN ST_NPoints(geometry) > 5000 ';
- $sSQL .= ' THEN ST_SimplifyPreserveTopology(geometry, 0.0001) ';
- $sSQL .= ' ELSE geometry ';
- $sSQL .= ' END) as asgeojson';
-} else {
- $sSQL .= ' ST_AsGeoJSON(centroid) as asgeojson';
-}
-$sSQL .= ' FROM placex ';
-$sSQL .= " WHERE place_id = $iPlaceID";
-
-$aPointDetails = $oDB->getRow($sSQL, null, 'Could not get details of place object.');
-
-if (!$aPointDetails) {
- throw new \Exception('No place with that place ID found.', 404);
-}
-
-$aPointDetails['localname'] = $aPointDetails['localname']?$aPointDetails['localname']:$aPointDetails['housenumber'];
-
-// Get all alternative names (languages, etc)
-$sSQL = 'SELECT (each(name)).key,(each(name)).value FROM placex ';
-$sSQL .= "WHERE place_id = $iPlaceID ORDER BY (each(name)).key";
-$aPointDetails['aNames'] = $oDB->getAssoc($sSQL);
-
-// Address tags
-$sSQL = 'SELECT (each(address)).key as key,(each(address)).value FROM placex ';
-$sSQL .= "WHERE place_id = $iPlaceID ORDER BY key";
-$aPointDetails['aAddressTags'] = $oDB->getAssoc($sSQL);
-
-// Extra tags
-$sSQL = 'SELECT (each(extratags)).key,(each(extratags)).value FROM placex ';
-$sSQL .= "WHERE place_id = $iPlaceID ORDER BY (each(extratags)).key";
-$aPointDetails['aExtraTags'] = $oDB->getAssoc($sSQL);
-
-// Address
-$aAddressLines = false;
-if ($bIncludeAddressDetails) {
- $oDetails = new Nominatim\AddressDetails($oDB, $iPlaceID, -1, $sLanguagePrefArraySQL);
- $aAddressLines = $oDetails->getAddressDetails(true);
-}
-
-// Linked places
-$aLinkedLines = false;
-if ($bIncludeLinkedPlaces) {
- $sSQL = 'SELECT placex.place_id, osm_type, osm_id, class, type, housenumber,';
- $sSQL .= ' admin_level, rank_address, ';
- $sSQL .= " ST_GeometryType(geometry) in ('ST_Polygon','ST_MultiPolygon') AS isarea,";
- $sSQL .= " ST_DistanceSpheroid(geometry, placegeometry, 'SPHEROID[\"WGS 84\",6378137,298.257223563, AUTHORITY[\"EPSG\",\"7030\"]]') AS distance, ";
- $sSQL .= " get_name_by_language(name,$sLanguagePrefArraySQL) AS localname, ";
- $sSQL .= ' length(name::text) AS namelength ';
- $sSQL .= ' FROM ';
- $sSQL .= ' placex, ';
- $sSQL .= ' ( ';
- $sSQL .= ' SELECT centroid AS placegeometry ';
- $sSQL .= ' FROM placex ';
- $sSQL .= " WHERE place_id = $iPlaceID ";
- $sSQL .= ' ) AS x';
- $sSQL .= " WHERE linked_place_id = $iPlaceID";
- $sSQL .= ' ORDER BY ';
- $sSQL .= ' rank_address ASC, ';
- $sSQL .= ' rank_search ASC, ';
- $sSQL .= " get_name_by_language(name, $sLanguagePrefArraySQL), ";
- $sSQL .= ' housenumber';
- $aLinkedLines = $oDB->getAll($sSQL);
-}
-
-// All places this is an immediate parent of
-$aHierarchyLines = false;
-if ($bIncludeHierarchy) {
- $sSQL = 'SELECT obj.place_id, osm_type, osm_id, class, type, housenumber,';
- $sSQL .= " admin_level, rank_address, ST_GeometryType(geometry) in ('ST_Polygon','ST_MultiPolygon') AS isarea,";
- $sSQL .= " ST_DistanceSpheroid(geometry, placegeometry, 'SPHEROID[\"WGS 84\",6378137,298.257223563, AUTHORITY[\"EPSG\",\"7030\"]]') AS distance, ";
- $sSQL .= " get_name_by_language(name,$sLanguagePrefArraySQL) AS localname, ";
- $sSQL .= ' length(name::text) AS namelength ';
- $sSQL .= ' FROM ';
- $sSQL .= ' ( ';
- $sSQL .= ' SELECT placex.place_id, osm_type, osm_id, class, type, housenumber, admin_level, rank_address, rank_search, geometry, name ';
- $sSQL .= ' FROM placex ';
- $sSQL .= " WHERE parent_place_id = $iPlaceID ";
- $sSQL .= ' ORDER BY ';
- $sSQL .= ' rank_address ASC, ';
- $sSQL .= ' rank_search ASC ';
- $sSQL .= ' LIMIT 500 ';
- $sSQL .= ' ) AS obj,';
- $sSQL .= ' ( ';
- $sSQL .= ' SELECT centroid AS placegeometry ';
- $sSQL .= ' FROM placex ';
- $sSQL .= " WHERE place_id = $iPlaceID ";
- $sSQL .= ' ) AS x';
- $sSQL .= ' ORDER BY ';
- $sSQL .= ' rank_address ASC, ';
- $sSQL .= ' rank_search ASC, ';
- $sSQL .= ' localname, ';
- $sSQL .= ' housenumber';
- $aHierarchyLines = $oDB->getAll($sSQL);
-}
-
-$aPlaceSearchNameKeywords = false;
-$aPlaceSearchAddressKeywords = false;
-if ($bIncludeKeywords) {
- $sSQL = "SELECT * FROM search_name WHERE place_id = $iPlaceID";
- $aPlaceSearchName = $oDB->getRow($sSQL);
-
- if (!empty($aPlaceSearchName)) {
- $sWordIds = substr($aPlaceSearchName['name_vector'], 1, -1);
- if (!empty($sWordIds)) {
- $sSQL = 'SELECT * FROM word WHERE word_id in ('.$sWordIds.')';
- $aPlaceSearchNameKeywords = $oDB->getAll($sSQL);
- }
-
- $sWordIds = substr($aPlaceSearchName['nameaddress_vector'], 1, -1);
- if (!empty($sWordIds)) {
- $sSQL = 'SELECT * FROM word WHERE word_id in ('.$sWordIds.')';
- $aPlaceSearchAddressKeywords = $oDB->getAll($sSQL);
- }
- }
-}
-
-logEnd($oDB, $hLog, 1);
-
-include(CONST_LibDir.'/template/details-'.$sOutputFormat.'.php');
+++ /dev/null
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-require_once(CONST_LibDir.'/init-website.php');
-require_once(CONST_LibDir.'/log.php');
-require_once(CONST_LibDir.'/PlaceLookup.php');
-require_once(CONST_LibDir.'/output.php');
-ini_set('memory_limit', '200M');
-
-$oParams = new Nominatim\ParameterParser();
-
-// Format for output
-$sOutputFormat = $oParams->getSet('format', array('xml', 'json', 'jsonv2', 'geojson', 'geocodejson'), 'xml');
-set_exception_handler_by_format($sOutputFormat);
-
-// Preferred language
-$aLangPrefOrder = $oParams->getPreferredLanguages();
-
-$oDB = new Nominatim\DB(CONST_Database_DSN);
-$oDB->connect();
-
-$hLog = logStart($oDB, 'place', $_SERVER['QUERY_STRING'], $aLangPrefOrder);
-
-$aSearchResults = array();
-$aCleanedQueryParts = array();
-
-$oPlaceLookup = new Nominatim\PlaceLookup($oDB);
-$oPlaceLookup->loadParamArray($oParams);
-$oPlaceLookup->setIncludeAddressDetails($oParams->getBool('addressdetails', true));
-
-$aOsmIds = explode(',', $oParams->getString('osm_ids', ''));
-
-if (count($aOsmIds) > CONST_Places_Max_ID_count) {
- userError('Bulk User: Only ' . CONST_Places_Max_ID_count . ' ids are allowed in one request.');
-}
-
-foreach ($aOsmIds as $sItem) {
- // Skip empty sItem
- if (empty($sItem)) {
- continue;
- }
-
- $sType = $sItem[0];
- $iId = (int) substr($sItem, 1);
- if ($iId > 0 && ($sType == 'N' || $sType == 'W' || $sType == 'R')) {
- $aCleanedQueryParts[] = $sType . $iId;
- $oPlace = $oPlaceLookup->lookupOSMID($sType, $iId);
- if ($oPlace) {
- // we want to use the search-* output templates, so we need to fill
- // $aSearchResults and slightly change the (reverse search) oPlace
- // key names
- $oResult = $oPlace;
- unset($oResult['aAddress']);
- if (isset($oPlace['aAddress'])) {
- $oResult['address'] = $oPlace['aAddress'];
- }
- if ($sOutputFormat != 'geocodejson') {
- unset($oResult['langaddress']);
- $oResult['name'] = $oPlace['langaddress'];
- }
-
- $aOutlineResult = $oPlaceLookup->getOutlines(
- $oPlace['place_id'],
- $oPlace['lon'],
- $oPlace['lat'],
- Nominatim\ClassTypes\getDefRadius($oPlace)
- );
-
- if ($aOutlineResult) {
- $oResult = array_merge($oResult, $aOutlineResult);
- }
-
- $aSearchResults[] = $oResult;
- }
- }
-}
-
-
-if (CONST_Debug) {
- exit;
-}
-
-$sXmlRootTag = 'lookupresults';
-$sQuery = join(',', $aCleanedQueryParts);
-// we initialize these to avoid warnings in our logfile
-$sViewBox = '';
-$bShowPolygons = '';
-$aExcludePlaceIDs = array();
-$sMoreURL = '';
-
-logEnd($oDB, $hLog, 1);
-
-$sOutputTemplate = ($sOutputFormat == 'jsonv2') ? 'json' : $sOutputFormat;
-include(CONST_LibDir.'/template/search-'.$sOutputTemplate.'.php');
+++ /dev/null
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-require_once(CONST_LibDir.'/init-website.php');
-require_once(CONST_LibDir.'/log.php');
-require_once(CONST_LibDir.'/output.php');
-ini_set('memory_limit', '200M');
-
-$oParams = new Nominatim\ParameterParser();
-$sOutputFormat = $oParams->getSet('format', array('json'), 'json');
-set_exception_handler_by_format($sOutputFormat);
-
-$iDays = $oParams->getInt('days', false);
-$bReduced = $oParams->getBool('reduced', false);
-$sClass = $oParams->getString('class', false);
-
-$oDB = new Nominatim\DB(CONST_Database_DSN);
-$oDB->connect();
-
-$iTotalBroken = (int) $oDB->getOne('SELECT count(*) FROM import_polygon_error');
-
-$aPolygons = array();
-while ($iTotalBroken && empty($aPolygons)) {
- $sSQL = 'SELECT osm_type, osm_id, class, type, name->\'name\' as "name",';
- $sSQL .= 'country_code, errormessage, updated';
- $sSQL .= ' FROM import_polygon_error';
-
- $aWhere = array();
- if ($iDays) {
- $aWhere[] = "updated > 'now'::timestamp - '".$iDays." day'::interval";
- $iDays++;
- }
-
- if ($bReduced) {
- $aWhere[] = "errormessage like 'Area reduced%'";
- }
- if ($sClass) {
- $sWhere[] = "class = '".pg_escape_string($sClass)."'";
- }
-
- if (!empty($aWhere)) {
- $sSQL .= ' WHERE '.join(' and ', $aWhere);
- }
-
- $sSQL .= ' ORDER BY updated desc LIMIT 1000';
- $aPolygons = $oDB->getAll($sSQL);
-}
-
-if (CONST_Debug) {
- var_dump($aPolygons);
- exit;
-}
-
-if ($sOutputFormat == 'json') {
- javascript_renderData($aPolygons);
-}
+++ /dev/null
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-require_once(CONST_LibDir.'/init-website.php');
-require_once(CONST_LibDir.'/ParameterParser.php');
-
-$oParams = new Nominatim\ParameterParser();
-
-// Format for output
-$sOutputFormat = $oParams->getSet('format', array('xml', 'json', 'jsonv2', 'geojson', 'geocodejson'), 'jsonv2');
-set_exception_handler_by_format($sOutputFormat);
-
-throw new Exception('Reverse-only import does not support forward searching.', 404);
+++ /dev/null
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-require_once(CONST_LibDir.'/init-website.php');
-require_once(CONST_LibDir.'/log.php');
-require_once(CONST_LibDir.'/PlaceLookup.php');
-require_once(CONST_LibDir.'/ReverseGeocode.php');
-require_once(CONST_LibDir.'/output.php');
-ini_set('memory_limit', '200M');
-
-$oParams = new Nominatim\ParameterParser();
-
-// Format for output
-$sOutputFormat = $oParams->getSet('format', array('xml', 'json', 'jsonv2', 'geojson', 'geocodejson'), 'xml');
-set_exception_handler_by_format($sOutputFormat);
-
-// Preferred language
-$aLangPrefOrder = $oParams->getPreferredLanguages();
-
-$oDB = new Nominatim\DB(CONST_Database_DSN);
-$oDB->connect();
-
-$hLog = logStart($oDB, 'reverse', $_SERVER['QUERY_STRING'], $aLangPrefOrder);
-
-$oPlaceLookup = new Nominatim\PlaceLookup($oDB);
-$oPlaceLookup->loadParamArray($oParams);
-$oPlaceLookup->setIncludeAddressDetails($oParams->getBool('addressdetails', true));
-
-$sOsmType = $oParams->getSet('osm_type', array('N', 'W', 'R'));
-$iOsmId = $oParams->getInt('osm_id', -1);
-$fLat = $oParams->getFloat('lat');
-$fLon = $oParams->getFloat('lon');
-$iZoom = $oParams->getInt('zoom', 18);
-
-if ($sOsmType && $iOsmId > 0) {
- $aPlace = $oPlaceLookup->lookupOSMID($sOsmType, $iOsmId);
-} elseif ($fLat !== false && $fLon !== false) {
- $oReverseGeocode = new Nominatim\ReverseGeocode($oDB);
- $oReverseGeocode->setZoom($iZoom);
-
- $oLookup = $oReverseGeocode->lookup($fLat, $fLon);
-
- if ($oLookup) {
- $aPlaces = $oPlaceLookup->lookup(array($oLookup->iId => $oLookup));
- if (!empty($aPlaces)) {
- $aPlace = reset($aPlaces);
- }
- }
-} else {
- userError('Need coordinates or OSM object to lookup.');
-}
-
-if (isset($aPlace)) {
- $aOutlineResult = $oPlaceLookup->getOutlines(
- $aPlace['place_id'],
- $aPlace['lon'],
- $aPlace['lat'],
- Nominatim\ClassTypes\getDefRadius($aPlace),
- $fLat,
- $fLon
- );
-
- if ($aOutlineResult) {
- $aPlace = array_merge($aPlace, $aOutlineResult);
- }
-} else {
- $aPlace = array();
-}
-
-logEnd($oDB, $hLog, count($aPlace) ? 1 : 0);
-
-if (CONST_Debug) {
- var_dump($aPlace);
- exit;
-}
-
-if ($sOutputFormat == 'geocodejson') {
- $sQuery = $fLat.','.$fLon;
- if (isset($aPlace['place_id'])) {
- $fDistance = $oDB->getOne(
- 'SELECT ST_Distance(ST_SetSRID(ST_Point(:lon,:lat),4326), centroid) FROM placex where place_id = :placeid',
- array(':lon' => $fLon, ':lat' => $fLat, ':placeid' => $aPlace['place_id'])
- );
- }
-}
-
-$sOutputTemplate = ($sOutputFormat == 'jsonv2') ? 'json' : $sOutputFormat;
-include(CONST_LibDir.'/template/address-'.$sOutputTemplate.'.php');
+++ /dev/null
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-require_once(CONST_LibDir.'/init-website.php');
-require_once(CONST_LibDir.'/log.php');
-require_once(CONST_LibDir.'/Geocode.php');
-require_once(CONST_LibDir.'/output.php');
-ini_set('memory_limit', '200M');
-
-$oDB = new Nominatim\DB(CONST_Database_DSN);
-$oDB->connect();
-$oParams = new Nominatim\ParameterParser();
-
-$oGeocode = new Nominatim\Geocode($oDB);
-
-$aLangPrefOrder = $oParams->getPreferredLanguages();
-$oGeocode->setLanguagePreference($aLangPrefOrder);
-
-// Format for output
-$sOutputFormat = $oParams->getSet('format', array('xml', 'json', 'jsonv2', 'geojson', 'geocodejson'), 'jsonv2');
-set_exception_handler_by_format($sOutputFormat);
-
-$oGeocode->loadParamArray($oParams, null);
-
-if (CONST_Search_BatchMode && isset($_GET['batch'])) {
- $aBatch = json_decode($_GET['batch'], true);
- $aBatchResults = array();
- foreach ($aBatch as $aBatchParams) {
- $oBatchGeocode = clone $oGeocode;
- $oBatchParams = new Nominatim\ParameterParser($aBatchParams);
- $oBatchGeocode->loadParamArray($oBatchParams);
- $oBatchGeocode->setQueryFromParams($oBatchParams);
- $aSearchResults = $oBatchGeocode->lookup();
- $aBatchResults[] = $aSearchResults;
- }
- include(CONST_LibDir.'/template/search-batch-json.php');
- exit;
-}
-
-$oGeocode->setQueryFromParams($oParams);
-
-if (!$oGeocode->getQueryString()
- && isset($_SERVER['PATH_INFO'])
- && strlen($_SERVER['PATH_INFO']) > 0
- && $_SERVER['PATH_INFO'][0] == '/'
-) {
- $sQuery = substr(rawurldecode($_SERVER['PATH_INFO']), 1);
-
- // reverse order of '/' separated string
- $aPhrases = explode('/', $sQuery);
- $aPhrases = array_reverse($aPhrases);
- $sQuery = join(', ', $aPhrases);
- $oGeocode->setQuery($sQuery);
-}
-
-$hLog = logStart($oDB, 'search', $oGeocode->getQueryString(), $aLangPrefOrder);
-
-$aSearchResults = $oGeocode->lookup();
-
-logEnd($oDB, $hLog, count($aSearchResults));
-
-$sQuery = $oGeocode->getQueryString();
-
-$aMoreParams = $oGeocode->getMoreUrlParams();
-$aMoreParams['format'] = $sOutputFormat;
-if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
- $aMoreParams['accept-language'] = $_SERVER['HTTP_ACCEPT_LANGUAGE'];
-}
-
-if (isset($_SERVER['REQUEST_SCHEME'])
- && isset($_SERVER['HTTP_HOST'])
- && isset($_SERVER['DOCUMENT_URI'])
-) {
- $sMoreURL = $_SERVER['REQUEST_SCHEME'].'://'
- .$_SERVER['HTTP_HOST'].$_SERVER['DOCUMENT_URI'].'/?'
- .http_build_query($aMoreParams);
-} else {
- $sMoreURL = '/search.php?'.http_build_query($aMoreParams);
-}
-
-if (CONST_Debug) {
- exit;
-}
-
-$sOutputTemplate = ($sOutputFormat == 'jsonv2') ? 'json' : $sOutputFormat;
-include(CONST_LibDir.'/template/search-'.$sOutputTemplate.'.php');
+++ /dev/null
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-require_once(CONST_LibDir.'/init-website.php');
-require_once(CONST_LibDir.'/ParameterParser.php');
-require_once(CONST_LibDir.'/Status.php');
-
-$oParams = new Nominatim\ParameterParser();
-$sOutputFormat = $oParams->getSet('format', array('text', 'json'), 'text');
-
-$oDB = new Nominatim\DB(CONST_Database_DSN);
-
-if ($sOutputFormat == 'json') {
- header('content-type: application/json; charset=UTF-8');
-}
-
-
-try {
- $oStatus = new Nominatim\Status($oDB);
- $oStatus->status();
-
- if ($sOutputFormat == 'json') {
- $epoch = $oStatus->dataDate();
- $aResponse = array(
- 'status' => 0,
- 'message' => 'OK',
- 'data_updated' => (new DateTime('@'.$epoch))->format(DateTime::RFC3339),
- 'software_version' => CONST_NominatimVersion
- );
- $sDatabaseVersion = $oStatus->databaseVersion();
- if ($sDatabaseVersion) {
- $aResponse['database_version'] = $sDatabaseVersion;
- }
- javascript_renderData($aResponse);
- } else {
- echo 'OK';
- }
-} catch (Exception $oErr) {
- if ($sOutputFormat == 'json') {
- $aResponse = array(
- 'status' => $oErr->getCode(),
- 'message' => $oErr->getMessage()
- );
- javascript_renderData($aResponse);
- } else {
- header('HTTP/1.0 500 Internal Server Error');
- echo 'ERROR: '.$oErr->getMessage();
- }
-}