2017-10-06 00:03:03 +03:00
< ? php
2022-01-03 18:23:58 +03:00
/**
* 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 .
*/
2017-10-06 00:03:03 +03:00
2017-10-08 18:00:59 +03:00
namespace Nominatim ;
2017-10-06 00:03:03 +03:00
2020-12-15 12:09:55 +03:00
require_once ( CONST_LibDir . '/SpecialSearchOperator.php' );
require_once ( CONST_LibDir . '/SearchContext.php' );
require_once ( CONST_LibDir . '/Result.php' );
2017-10-08 18:13:41 +03:00
2017-10-06 00:03:03 +03:00
/**
* 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 ();
2018-05-06 23:10:38 +03:00
/// True if the name is rare enough to force index use on name.
private $bRareName = false ;
2021-10-26 11:23:55 +03:00
/// True if the name requires to be accompanied by address terms.
private $bNameNeedsAddress = false ;
2017-10-06 00:03:03 +03:00
/// 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 = '' ;
2017-10-08 22:23:31 +03:00
/// Global search constraints.
private $oContext ;
2017-10-06 00:03:03 +03:00
// Temporary values used while creating the search description.
2017-10-08 22:23:31 +03:00
/// Index of phrase currently processed.
2017-10-06 00:03:03 +03:00
private $iNamePhrase = - 1 ;
2017-10-06 01:14:48 +03:00
2017-10-10 01:15:56 +03:00
/**
* Create an empty search description .
*
* @ param object $oContext Global context to use . Will be inherited by
* all derived search objects .
*/
2017-10-08 22:23:31 +03:00
public function __construct ( $oContext )
{
$this -> oContext = $oContext ;
}
2017-10-10 01:15:56 +03:00
/**
* Get current search rank .
*
2018-02-18 16:11:35 +03:00
* The higher the search rank the lower the likelihood that the
2017-10-10 01:15:56 +03:00
* search is a correct interpretation of the search query .
*
* @ return integer Search rank .
*/
2017-10-07 13:01:56 +03:00
public function getRank ()
2017-10-06 01:14:48 +03:00
{
return $this -> iSearchRank ;
}
2017-10-10 01:15:56 +03:00
/**
* 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 .
*/
2017-10-07 13:01:56 +03:00
public function extractKeyValuePairs ( $sQuery )
2017-10-06 01:14:48 +03:00
{
// 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 ;
}
2017-10-07 13:01:56 +03:00
2017-10-10 01:15:56 +03:00
/**
* Check if the combination of parameters is sensible .
*
* @ return bool True , if the search looks valid .
*/
2017-10-13 23:23:39 +03:00
public function isValidSearch ()
2017-10-08 13:57:22 +03:00
{
2018-03-22 14:36:24 +03:00
if ( empty ( $this -> aName )) {
2017-10-08 13:57:22 +03:00
if ( $this -> sHouseNumber ) {
return false ;
}
2017-10-13 23:23:39 +03:00
if ( ! $this -> sClass && ! $this -> sCountryCode ) {
return false ;
}
2017-10-08 13:57:22 +03:00
}
2021-10-26 11:23:55 +03:00
if ( $this -> bNameNeedsAddress && empty ( $this -> aAddress )) {
return false ;
}
2017-10-08 13:57:22 +03:00
return true ;
}
/////////// Search building functions
2021-07-18 17:10:42 +03:00
/**
* 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 .
*/
2021-07-17 21:24:33 +03:00
public function clone ( $iTermCost )
{
$oSearch = clone $this ;
$oSearch -> iSearchRank += $iTermCost ;
2017-10-08 13:57:22 +03:00
2021-07-17 21:24:33 +03:00
return $oSearch ;
}
2017-10-08 18:00:59 +03:00
2021-07-18 17:10:42 +03:00
/**
* 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 .
*/
2021-07-17 21:24:33 +03:00
public function hasName ( $bIncludeNonNames = false )
2017-10-08 13:57:22 +03:00
{
2021-07-17 21:24:33 +03:00
return ! empty ( $this -> aName )
|| ( ! empty ( $this -> aNameNonSearch ) && $bIncludeNonNames );
}
2021-07-18 17:10:42 +03:00
/**
* Check if the search currently includes an address term .
*
* @ return bool True , if any address term is included , including stop - word
* terms .
*/
2021-07-17 21:24:33 +03:00
public function hasAddress ()
{
return ! empty ( $this -> aAddress ) || ! empty ( $this -> aAddressNonSearch );
}
2021-07-18 17:10:42 +03:00
/**
* Check if a country restriction is currently included in the search .
*
* @ return bool True , if a country restriction is set .
*/
2021-07-17 21:24:33 +03:00
public function hasCountry ()
{
return $this -> sCountryCode !== '' ;
}
2021-07-18 17:10:42 +03:00
/**
* Check if a postcode is currently included in the search .
*
* @ return bool True , if a postcode is set .
*/
2021-07-17 21:24:33 +03:00
public function hasPostcode ()
{
return $this -> sPostcode !== '' ;
}
2021-07-18 17:10:42 +03:00
/**
* Check if a house number is set for the search .
*
* @ return bool True , if a house number is set .
*/
2021-07-17 21:24:33 +03:00
public function hasHousenumber ()
{
return $this -> sHouseNumber !== '' ;
}
2021-07-18 17:10:42 +03:00
/**
* 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 .
*/
2021-07-17 21:24:33 +03:00
public function hasOperator ( $iOperator = null )
{
return $iOperator === null ? $this -> iOperator != Operator :: NONE : $this -> iOperator == $iOperator ;
}
2021-07-18 17:10:42 +03:00
/**
* 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 ) .
*/
2021-07-17 21:24:33 +03:00
public function addAddressToken ( $iId , $bSearchable = true )
{
if ( $bSearchable ) {
$this -> aAddress [ $iId ] = $iId ;
} else {
$this -> aAddressNonSearch [ $iId ] = $iId ;
2017-10-08 13:57:22 +03:00
}
2021-07-17 21:24:33 +03:00
}
2021-07-18 17:10:42 +03:00
/**
* Add the given full - word token to the list of terms to search for in the
* name .
*
2021-07-18 17:52:37 +03:00
* @ param interger iId ID of term to add .
* @ param bool bRareName True if the term is infrequent enough to not
* require other constraints for efficient search .
2021-07-18 17:10:42 +03:00
*/
2021-07-18 17:52:37 +03:00
public function addNameToken ( $iId , $bRareName )
2021-07-17 21:24:33 +03:00
{
$this -> aName [ $iId ] = $iId ;
2021-07-18 17:52:37 +03:00
$this -> bRareName = $bRareName ;
2021-10-26 11:28:28 +03:00
$this -> bNameNeedsAddress = false ;
2021-07-17 21:24:33 +03:00
}
2021-07-18 17:10:42 +03:00
/**
* 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 ) .
2021-10-26 11:23:55 +03:00
* @ 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 .
2021-07-18 17:10:42 +03:00
* @ param integer iPhraseNumber Index of phrase , where the partial term
* appears .
*/
2021-10-26 11:23:55 +03:00
public function addPartialNameToken ( $iId , $bSearchable , $bNeedsAddress , $iPhraseNumber )
2021-07-17 21:24:33 +03:00
{
2021-10-26 11:23:55 +03:00
if ( empty ( $this -> aName )) {
$this -> bNameNeedsAddress = $bNeedsAddress ;
} else {
2021-11-06 00:18:37 +03:00
$this -> bNameNeedsAddress &= $bNeedsAddress ;
2021-10-26 11:23:55 +03:00
}
2021-07-17 21:24:33 +03:00
if ( $bSearchable ) {
$this -> aName [ $iId ] = $iId ;
} else {
$this -> aNameNonSearch [ $iId ] = $iId ;
}
$this -> iNamePhrase = $iPhraseNumber ;
}
2021-07-18 17:10:42 +03:00
/**
* Set country restriction for the search .
*
* @ param string sCountryCode Country code of country to restrict search to .
*/
2021-07-17 21:24:33 +03:00
public function setCountry ( $sCountryCode )
{
$this -> sCountryCode = $sCountryCode ;
$this -> iNamePhrase = - 1 ;
}
2021-07-18 17:10:42 +03:00
/**
* Set postcode search constraint .
*
* @ param string sPostcode Postcode the result should have .
*/
2021-07-17 21:24:33 +03:00
public function setPostcode ( $sPostcode )
{
$this -> sPostcode = $sPostcode ;
$this -> iNamePhrase = - 1 ;
}
2021-07-18 17:10:42 +03:00
/**
* Make this search a search for a postcode object .
*
* @ param integer iId Token Id for the postcode .
* @ param string sPostcode Postcode to look for .
*/
2021-07-17 21:24:33 +03:00
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 ;
}
2021-07-18 17:10:42 +03:00
/**
* Set house number search cnstraint .
*
* @ param string sNumber House number the result should have .
*/
2021-07-17 21:24:33 +03:00
public function setHousenumber ( $sNumber )
{
$this -> sHouseNumber = $sNumber ;
$this -> iNamePhrase = - 1 ;
}
2017-10-08 13:57:22 +03:00
2021-07-18 17:10:42 +03:00
/**
* Make this search a search for a house number .
*
* @ param integer iId Token Id for the house number .
*/
2021-07-17 21:24:33 +03:00
public function setHousenumberAsName ( $iId )
{
$this -> aAddress = array_merge ( $this -> aAddress , $this -> aName );
$this -> bRareName = false ;
2021-10-26 11:28:28 +03:00
$this -> bNameNeedsAddress = true ;
2021-07-17 21:24:33 +03:00
$this -> aName = array ( $iId => $iId );
$this -> iNamePhrase = - 1 ;
2017-10-08 13:57:22 +03:00
}
2017-10-10 01:15:56 +03:00
/**
2021-07-17 21:24:33 +03:00
* Make this search a POI search .
2017-10-10 01:15:56 +03:00
*
2021-07-17 21:24:33 +03:00
* 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 ) .
2017-10-10 01:15:56 +03:00
*
2021-07-17 21:24:33 +03:00
* @ 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
2017-10-10 01:15:56 +03:00
*/
2021-07-17 21:24:33 +03:00
public function setPoiSearch ( $iOperator , $sClass , $sType )
2017-10-08 13:57:22 +03:00
{
2021-07-17 21:24:33 +03:00
$this -> iOperator = $iOperator ;
$this -> sClass = $sClass ;
$this -> sType = $sType ;
$this -> iNamePhrase = - 1 ;
}
2017-10-08 13:57:22 +03:00
2021-07-17 21:24:33 +03:00
public function getNamePhrase ()
{
return $this -> iNamePhrase ;
}
2017-10-08 13:57:22 +03:00
2021-07-18 17:10:42 +03:00
/**
* Get the global search context .
*
* @ return object Objects of global search constraints .
*/
2021-07-17 21:24:33 +03:00
public function getContext ()
{
return $this -> oContext ;
2017-10-08 13:57:22 +03:00
}
/////////// Query functions
2017-10-10 00:12:13 +03:00
2017-10-10 01:15:56 +03:00
/**
* Query database for places that match this search .
*
2019-03-10 17:42:58 +03:00
* @ param object $oDB Nominatim :: DB instance to use .
2018-05-06 23:10:38 +03:00
* @ 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 .
2017-10-10 01:15:56 +03:00
*
* @ return mixed [] An array with two fields : IDs contains the list of
* matching place IDs and houseNumber the houseNumber
* if appicable or - 1 if not .
*/
2018-05-06 23:10:38 +03:00
public function query ( & $oDB , $iMinRank , $iMaxRank , $iLimit )
2017-10-09 23:55:50 +03:00
{
2017-10-19 00:54:47 +03:00
$aResults = array ();
2017-10-09 23:55:50 +03:00
if ( $this -> sCountryCode
2018-03-22 14:36:24 +03:00
&& empty ( $this -> aName )
2017-10-09 23:55:50 +03:00
&& ! $this -> iOperator
&& ! $this -> sClass
&& ! $this -> oContext -> hasNearPoint ()
) {
// Just looking for a country - look it up
if ( 4 >= $iMinRank && 4 <= $iMaxRank ) {
2017-10-19 00:54:47 +03:00
$aResults = $this -> queryCountry ( $oDB );
2017-10-09 23:55:50 +03:00
}
2018-03-22 14:36:24 +03:00
} elseif ( empty ( $this -> aName ) && empty ( $this -> aAddress )) {
2017-10-09 23:55:50 +03:00
// Neither name nor address? Then we must be
// looking for a POI in a geographic area.
if ( $this -> oContext -> isBoundedSearch ()) {
2017-10-19 00:54:47 +03:00
$aResults = $this -> queryNearbyPoi ( $oDB , $iLimit );
2017-10-09 23:55:50 +03:00
}
} elseif ( $this -> iOperator == Operator :: POSTCODE ) {
// looking for postcode
2017-10-19 00:54:47 +03:00
$aResults = $this -> queryPostcode ( $oDB , $iLimit );
2017-10-09 23:55:50 +03:00
} else {
// Ordinary search:
// First search for places according to name and address.
2017-10-19 00:54:47 +03:00
$aResults = $this -> queryNamedPlace (
2017-10-09 23:55:50 +03:00
$oDB ,
$iMinRank ,
$iMaxRank ,
$iLimit
);
// finally get POIs if requested
2018-03-22 14:36:24 +03:00
if ( $this -> sClass && ! empty ( $aResults )) {
2017-10-19 00:54:47 +03:00
$aResults = $this -> queryPoiByOperator ( $oDB , $aResults , $iLimit );
2017-10-09 23:55:50 +03:00
}
}
2018-03-24 19:44:13 +03:00
Debug :: printDebugTable ( 'Place IDs' , $aResults );
2017-10-09 23:55:50 +03:00
2018-03-22 14:36:24 +03:00
if ( ! empty ( $aResults ) && $this -> sPostcode ) {
2017-10-19 00:54:47 +03:00
$sPlaceIds = Result :: joinIdsByTable ( $aResults , Result :: TABLE_PLACEX );
if ( $sPlaceIds ) {
$sSQL = 'SELECT place_id FROM placex' ;
$sSQL .= ' WHERE place_id in (' . $sPlaceIds . ')' ;
2018-11-17 01:52:19 +03:00
$sSQL .= " AND postcode != ' " . $this -> sPostcode . " ' " ;
2018-03-24 19:44:13 +03:00
Debug :: printSQL ( $sSQL );
2019-03-10 17:42:58 +03:00
$aFilteredPlaceIDs = $oDB -> getCol ( $sSQL );
2017-10-19 00:54:47 +03:00
if ( $aFilteredPlaceIDs ) {
foreach ( $aFilteredPlaceIDs as $iPlaceId ) {
2018-11-17 01:52:19 +03:00
$aResults [ $iPlaceId ] -> iResultRank ++ ;
2017-10-19 00:54:47 +03:00
}
2017-10-09 23:55:50 +03:00
}
}
}
2017-10-19 00:54:47 +03:00
return $aResults ;
2017-10-09 23:55:50 +03:00
}
2017-10-08 18:00:59 +03:00
2017-10-09 23:55:50 +03:00
private function queryCountry ( & $oDB )
2017-10-07 13:01:56 +03:00
{
$sSQL = 'SELECT place_id FROM placex ' ;
$sSQL .= " WHERE country_code=' " . $this -> sCountryCode . " ' " ;
$sSQL .= ' AND rank_search = 4' ;
2017-10-08 23:44:01 +03:00
if ( $this -> oContext -> bViewboxBounded ) {
$sSQL .= ' AND ST_Intersects(' . $this -> oContext -> sqlViewboxSmall . ', geometry)' ;
2017-10-07 13:01:56 +03:00
}
2017-10-26 22:21:21 +03:00
$sSQL .= ' ORDER BY st_area(geometry) DESC LIMIT 1' ;
2017-10-07 13:01:56 +03:00
2018-03-24 19:44:13 +03:00
Debug :: printSQL ( $sSQL );
2017-10-07 13:01:56 +03:00
2019-02-24 18:14:36 +03:00
$iPlaceId = $oDB -> getOne ( $sSQL );
2017-10-19 00:54:47 +03:00
$aResults = array ();
2019-02-24 18:14:36 +03:00
if ( $iPlaceId ) {
2017-10-19 00:54:47 +03:00
$aResults [ $iPlaceId ] = new Result ( $iPlaceId );
}
return $aResults ;
2017-10-07 13:01:56 +03:00
}
2017-10-09 23:55:50 +03:00
private function queryNearbyPoi ( & $oDB , $iLimit )
2017-10-07 13:01:56 +03:00
{
if ( ! $this -> sClass ) {
return array ();
}
2017-10-19 00:54:47 +03:00
$aDBResults = array ();
2017-10-07 13:01:56 +03:00
$sPoiTable = $this -> poiTable ();
2019-03-10 17:42:58 +03:00
if ( $oDB -> tableExists ( $sPoiTable )) {
2017-10-07 13:01:56 +03:00
$sSQL = 'SELECT place_id FROM ' . $sPoiTable . ' ct' ;
2017-10-09 00:33:54 +03:00
if ( $this -> oContext -> sqlCountryList ) {
2017-10-07 13:01:56 +03:00
$sSQL .= ' JOIN placex USING (place_id)' ;
}
2017-10-08 22:23:31 +03:00
if ( $this -> oContext -> hasNearPoint ()) {
$sSQL .= ' WHERE ' . $this -> oContext -> withinSQL ( 'ct.centroid' );
2017-10-10 00:12:13 +03:00
} elseif ( $this -> oContext -> bViewboxBounded ) {
2017-10-08 23:44:01 +03:00
$sSQL .= ' WHERE ST_Contains(' . $this -> oContext -> sqlViewboxSmall . ', ct.centroid)' ;
2017-10-07 13:01:56 +03:00
}
2017-10-09 00:33:54 +03:00
if ( $this -> oContext -> sqlCountryList ) {
$sSQL .= ' AND country_code in ' . $this -> oContext -> sqlCountryList ;
2017-10-07 13:01:56 +03:00
}
2017-10-09 00:15:06 +03:00
$sSQL .= $this -> oContext -> excludeSQL ( ' AND place_id' );
2017-10-08 23:44:01 +03:00
if ( $this -> oContext -> sqlViewboxCentre ) {
$sSQL .= ' ORDER BY ST_Distance(' ;
$sSQL .= $this -> oContext -> sqlViewboxCentre . ', ct.centroid) ASC' ;
2017-10-08 22:23:31 +03:00
} elseif ( $this -> oContext -> hasNearPoint ()) {
$sSQL .= ' ORDER BY ' . $this -> oContext -> distanceSQL ( 'ct.centroid' ) . ' ASC' ;
2017-10-07 13:01:56 +03:00
}
2019-03-10 17:42:58 +03:00
$sSQL .= " LIMIT $iLimit " ;
2018-03-24 19:44:13 +03:00
Debug :: printSQL ( $sSQL );
2019-03-10 17:42:58 +03:00
$aDBResults = $oDB -> getCol ( $sSQL );
2017-10-07 13:01:56 +03:00
}
2017-10-08 22:23:31 +03:00
if ( $this -> oContext -> hasNearPoint ()) {
2017-10-07 13:01:56 +03:00
$sSQL = 'SELECT place_id FROM placex WHERE ' ;
2019-03-10 17:42:58 +03:00
$sSQL .= 'class = :class and type = :type' ;
2017-10-08 22:23:31 +03:00
$sSQL .= ' AND ' . $this -> oContext -> withinSQL ( 'geometry' );
2017-10-07 13:01:56 +03:00
$sSQL .= ' AND linked_place_id is null' ;
2017-10-09 00:33:54 +03:00
if ( $this -> oContext -> sqlCountryList ) {
$sSQL .= ' AND country_code in ' . $this -> oContext -> sqlCountryList ;
2017-10-07 13:01:56 +03:00
}
2017-10-26 22:21:21 +03:00
$sSQL .= ' ORDER BY ' . $this -> oContext -> distanceSQL ( 'centroid' ) . ' ASC' ;
2017-10-07 13:01:56 +03:00
$sSQL .= " LIMIT $iLimit " ;
2018-03-24 19:44:13 +03:00
Debug :: printSQL ( $sSQL );
2019-03-10 17:42:58 +03:00
$aDBResults = $oDB -> getCol (
$sSQL ,
array ( ':class' => $this -> sClass , ':type' => $this -> sType )
);
2017-10-07 13:01:56 +03:00
}
2017-10-19 00:54:47 +03:00
$aResults = array ();
foreach ( $aDBResults as $iPlaceId ) {
$aResults [ $iPlaceId ] = new Result ( $iPlaceId );
}
return $aResults ;
2017-10-07 13:01:56 +03:00
}
2017-10-09 23:55:50 +03:00
private function queryPostcode ( & $oDB , $iLimit )
2017-10-07 13:01:56 +03:00
{
2017-10-08 18:00:59 +03:00
$sSQL = 'SELECT p.place_id FROM location_postcode p ' ;
2017-10-07 13:01:56 +03:00
2018-03-22 14:36:24 +03:00
if ( ! empty ( $this -> aAddress )) {
2017-10-07 13:01:56 +03:00
$sSQL .= ', search_name s ' ;
$sSQL .= 'WHERE s.place_id = p.parent_place_id ' ;
$sSQL .= 'AND array_cat(s.nameaddress_vector, s.name_vector)' ;
2019-02-24 18:14:36 +03:00
$sSQL .= ' @> ' . $oDB -> getArraySQL ( $this -> aAddress ) . ' AND ' ;
2017-10-07 13:01:56 +03:00
} else {
$sSQL .= 'WHERE ' ;
}
2017-10-08 18:36:38 +03:00
$sSQL .= " p.postcode = ' " . reset ( $this -> aName ) . " ' " ;
2017-10-09 00:33:54 +03:00
$sSQL .= $this -> countryCodeSQL ( ' AND p.country_code' );
2019-04-02 15:49:31 +03:00
if ( $this -> oContext -> bViewboxBounded ) {
$sSQL .= ' AND ST_Intersects(' . $this -> oContext -> sqlViewboxSmall . ', geometry)' ;
}
2017-10-09 00:15:06 +03:00
$sSQL .= $this -> oContext -> excludeSQL ( ' AND p.place_id' );
2017-10-07 13:01:56 +03:00
$sSQL .= " LIMIT $iLimit " ;
2018-03-24 19:44:13 +03:00
Debug :: printSQL ( $sSQL );
2017-10-07 13:01:56 +03:00
2017-10-19 00:54:47 +03:00
$aResults = array ();
2019-03-10 17:42:58 +03:00
foreach ( $oDB -> getCol ( $sSQL ) as $iPlaceId ) {
2017-10-19 00:54:47 +03:00
$aResults [ $iPlaceId ] = new Result ( $iPlaceId , Result :: TABLE_POSTCODE );
}
return $aResults ;
2017-10-07 13:01:56 +03:00
}
2018-05-06 23:10:38 +03:00
private function queryNamedPlace ( & $oDB , $iMinAddressRank , $iMaxAddressRank , $iLimit )
2017-10-07 13:01:56 +03:00
{
$aTerms = array ();
$aOrder = array ();
2018-03-22 14:36:24 +03:00
if ( ! empty ( $this -> aName )) {
2019-02-24 18:14:36 +03:00
$aTerms [] = 'name_vector @> ' . $oDB -> getArraySQL ( $this -> aName );
2017-10-07 13:01:56 +03:00
}
2018-03-22 14:36:24 +03:00
if ( ! empty ( $this -> aAddress )) {
2017-10-07 13:01:56 +03:00
// For infrequent name terms disable index usage for address
2018-05-06 23:10:38 +03:00
if ( $this -> bRareName ) {
2019-02-24 18:14:36 +03:00
$aTerms [] = 'array_cat(nameaddress_vector,ARRAY[]::integer[]) @> ' . $oDB -> getArraySQL ( $this -> aAddress );
2017-10-07 13:01:56 +03:00
} else {
2019-02-24 18:14:36 +03:00
$aTerms [] = 'nameaddress_vector @> ' . $oDB -> getArraySQL ( $this -> aAddress );
2017-10-07 13:01:56 +03:00
}
}
2017-10-09 00:33:54 +03:00
$sCountryTerm = $this -> countryCodeSQL ( 'country_code' );
2017-10-07 13:01:56 +03:00
if ( $sCountryTerm ) {
$aTerms [] = $sCountryTerm ;
}
if ( $this -> sHouseNumber ) {
2020-07-08 18:47:38 +03:00
$aTerms [] = 'address_rank between 16 and 30' ;
2017-10-07 13:01:56 +03:00
} elseif ( ! $this -> sClass || $this -> iOperator == Operator :: NAME ) {
if ( $iMinAddressRank > 0 ) {
2020-02-21 18:35:59 +03:00
$aTerms [] = " ((address_rank between $iMinAddressRank and $iMaxAddressRank ) or (search_rank between $iMinAddressRank and $iMaxAddressRank )) " ;
2017-10-07 13:01:56 +03:00
}
}
2017-10-08 22:23:31 +03:00
if ( $this -> oContext -> hasNearPoint ()) {
$aTerms [] = $this -> oContext -> withinSQL ( 'centroid' );
$aOrder [] = $this -> oContext -> distanceSQL ( 'centroid' );
2017-10-07 13:01:56 +03:00
} elseif ( $this -> sPostcode ) {
2018-03-22 14:36:24 +03:00
if ( empty ( $this -> aAddress )) {
2021-09-25 00:56:42 +03:00
$aTerms [] = " EXISTS(SELECT place_id FROM location_postcode p WHERE p.postcode = ' " . $this -> sPostcode . " ' AND ST_DWithin(search_name.centroid, p.geometry, 0.12)) " ;
2017-10-07 13:01:56 +03:00
} else {
$aOrder [] = " (SELECT min(ST_Distance(search_name.centroid, p.geometry)) FROM location_postcode p WHERE p.postcode = ' " . $this -> sPostcode . " ') " ;
}
}
2017-10-09 00:15:06 +03:00
$sExcludeSQL = $this -> oContext -> excludeSQL ( 'place_id' );
2017-10-07 13:01:56 +03:00
if ( $sExcludeSQL ) {
2017-10-09 00:15:06 +03:00
$aTerms [] = $sExcludeSQL ;
2017-10-07 13:01:56 +03:00
}
2017-10-08 23:44:01 +03:00
if ( $this -> oContext -> bViewboxBounded ) {
$aTerms [] = 'centroid && ' . $this -> oContext -> sqlViewboxSmall ;
2017-10-07 13:01:56 +03:00
}
if ( $this -> sHouseNumber ) {
$sImportanceSQL = '- abs(26 - address_rank) + 3' ;
} else {
2018-02-20 01:24:12 +03:00
$sImportanceSQL = '(CASE WHEN importance = 0 OR importance IS NULL THEN 0.75001-(search_rank::float/40) ELSE importance END)' ;
2017-10-07 13:01:56 +03:00
}
2017-10-08 23:44:01 +03:00
$sImportanceSQL .= $this -> oContext -> viewboxImportanceSQL ( 'centroid' );
2017-10-07 13:01:56 +03:00
$aOrder [] = " $sImportanceSQL DESC " ;
2020-11-25 13:44:25 +03:00
$aFullNameAddress = $this -> oContext -> getFullNameTerms ();
if ( ! empty ( $aFullNameAddress )) {
2017-10-07 13:01:56 +03:00
$sExactMatchSQL = ' ( ' ;
2017-10-08 11:06:17 +03:00
$sExactMatchSQL .= ' SELECT count(*) FROM ( ' ;
2020-11-25 13:44:25 +03:00
$sExactMatchSQL .= ' SELECT unnest(' . $oDB -> getArraySQL ( $aFullNameAddress ) . ')' ;
2017-10-08 11:06:17 +03:00
$sExactMatchSQL .= ' INTERSECT ' ;
$sExactMatchSQL .= ' SELECT unnest(nameaddress_vector)' ;
$sExactMatchSQL .= ' ) s' ;
2017-10-07 13:01:56 +03:00
$sExactMatchSQL .= ') as exactmatch' ;
$aOrder [] = 'exactmatch DESC' ;
} else {
$sExactMatchSQL = '0::int as exactmatch' ;
}
2022-02-09 00:35:12 +03:00
if ( empty ( $aTerms )) {
return array ();
2017-10-07 13:01:56 +03:00
}
2022-02-09 00:35:12 +03:00
if ( $this -> hasHousenumber ()) {
$sHouseNumberRegex = $oDB -> getDBQuoted ( '\\\\m' . $this -> sHouseNumber . '\\\\M' );
2017-10-07 13:01:56 +03:00
2022-02-09 00:35:12 +03:00
// 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 .= ' and housenumber ~* E' . $sHouseNumberRegex ;
2017-10-07 13:01:56 +03:00
2022-02-09 00:35:12 +03:00
// 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' ;
2022-02-09 23:42:28 +03:00
$sIpolHnr .= ' AND ' . $this -> sHouseNumber . ' between startnumber and endnumber' ;
$sIpolHnr .= ' AND (' . $this -> sHouseNumber . ' - startnumber) % step = 0' ;
2017-10-19 00:54:47 +03:00
2022-02-09 00:35:12 +03:00
$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' " ;
}
2017-10-19 00:54:47 +03:00
}
2017-10-07 13:01:56 +03:00
2022-02-09 00:35:12 +03:00
if ( $this -> sClass ) {
$iLimit = 40 ;
}
2017-10-07 13:01:56 +03:00
2022-02-09 00:35:12 +03:00
$sSelfHnr = 'SELECT * FROM placex WHERE place_id = search_name.place_id' ;
$sSelfHnr .= ' AND housenumber ~* E' . $sHouseNumberRegex ;
2021-06-17 13:05:33 +03:00
2022-02-09 00:35:12 +03:00
$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 ;
}
2017-10-19 00:54:47 +03:00
2022-02-09 00:35:12 +03:00
$sSQL = 'SELECT place_id, address_rank, ' . $sExactMatchSQL ;
$sSQL .= ' FROM search_name' ;
$sSQL .= ' WHERE ' . join ( ' and ' , $aTerms );
$sSQL .= ' ORDER BY ' . join ( ', ' , $aOrder );
$sSQL .= ' LIMIT ' . $iLimit ;
2017-10-19 00:54:47 +03:00
}
2017-10-07 13:01:56 +03:00
2018-03-24 19:44:13 +03:00
Debug :: printSQL ( $sSQL );
2017-10-07 13:01:56 +03:00
2022-02-09 00:35:12 +03:00
$aDBResults = $oDB -> getAll ( $sSQL , null , 'Could not get places for search terms.' );
2017-10-07 13:01:56 +03:00
2022-02-09 00:35:12 +03:00
$aResults = array ();
2017-10-07 13:01:56 +03:00
2022-02-09 00:35:12 +03:00
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 ;
}
}
2017-10-07 13:01:56 +03:00
2022-02-09 00:35:12 +03:00
if ( $aResult [ 'address_rank' ] < 26 ) {
$oResult -> iResultRank += 2 ;
} else {
$oResult -> iResultRank ++ ;
}
2017-10-07 13:01:56 +03:00
}
2022-02-09 00:35:12 +03:00
if ( $bNeedResult ) {
$aResults [ $aResult [ 'place_id' ]] = $oResult ;
2017-10-07 13:01:56 +03:00
}
}
2017-10-19 00:54:47 +03:00
return $aResults ;
2017-10-07 13:01:56 +03:00
}
2017-10-09 23:55:50 +03:00
private function queryPoiByOperator ( & $oDB , $aParentIDs , $iLimit )
2017-10-07 13:01:56 +03:00
{
2017-10-19 00:54:47 +03:00
$aResults = array ();
$sPlaceIDs = Result :: joinIdsByTable ( $aParentIDs , Result :: TABLE_PLACEX );
if ( ! $sPlaceIDs ) {
return $aResults ;
}
2017-10-07 13:01:56 +03:00
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 . " ' " ;
2017-10-26 22:21:21 +03:00
$sSQL .= ' AND linked_place_id is null' ;
2017-10-09 00:15:06 +03:00
$sSQL .= $this -> oContext -> excludeSQL ( ' AND place_id' );
2017-10-26 22:21:21 +03:00
$sSQL .= ' ORDER BY rank_search ASC ' ;
2017-10-07 13:01:56 +03:00
$sSQL .= " LIMIT $iLimit " ;
2018-03-24 19:44:13 +03:00
Debug :: printSQL ( $sSQL );
2017-10-07 13:01:56 +03:00
2019-03-10 17:42:58 +03:00
foreach ( $oDB -> getCol ( $sSQL ) as $iPlaceId ) {
2017-10-19 00:54:47 +03:00
$aResults [ $iPlaceId ] = new Result ( $iPlaceId );
}
2017-10-07 13:01:56 +03:00
}
// NEAR and IN are handled the same
if ( $this -> iOperator == Operator :: TYPE || $this -> iOperator == Operator :: NEAR ) {
$sClassTable = $this -> poiTable ();
2019-03-10 17:42:58 +03:00
$bCacheTable = $oDB -> tableExists ( $sClassTable );
2017-10-07 13:01:56 +03:00
$sSQL = " SELECT min(rank_search) FROM placex WHERE place_id in ( $sPlaceIDs ) " ;
2018-03-24 19:44:13 +03:00
Debug :: printSQL ( $sSQL );
2019-03-10 17:42:58 +03:00
$iMaxRank = ( int ) $oDB -> getOne ( $sSQL );
2017-10-07 13:01:56 +03:00
// 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_GeometryType(geometry) in ('ST_Polygon','ST_MultiPolygon') " ;
2017-10-26 22:21:21 +03:00
$sSQL .= ' ORDER BY rank_search ASC ' ;
$sSQL .= ' LIMIT 1' ;
2018-03-24 19:44:13 +03:00
Debug :: printSQL ( $sSQL );
2019-03-10 17:42:58 +03:00
$sPlaceGeom = $oDB -> getOne ( $sSQL );
2017-10-07 13:01:56 +03:00
}
if ( $sPlaceGeom ) {
$sPlaceIDs = false ;
} else {
$iMaxRank += 5 ;
$sSQL = 'SELECT place_id FROM placex' ;
$sSQL .= " WHERE place_id in ( $sPlaceIDs ) and rank_search < $iMaxRank " ;
2018-03-24 19:44:13 +03:00
Debug :: printSQL ( $sSQL );
2019-03-10 17:42:58 +03:00
$aPlaceIDs = $oDB -> getCol ( $sSQL );
2017-10-07 13:01:56 +03:00
$sPlaceIDs = join ( ',' , $aPlaceIDs );
}
if ( $sPlaceIDs || $sPlaceGeom ) {
$fRange = 0.01 ;
if ( $bCacheTable ) {
// More efficient - can make the range bigger
$fRange = 0.05 ;
$sOrderBySQL = '' ;
2017-10-08 22:23:31 +03:00
if ( $this -> oContext -> hasNearPoint ()) {
$sOrderBySQL = $this -> oContext -> distanceSQL ( 'l.centroid' );
2017-10-07 13:01:56 +03:00
} elseif ( $sPlaceIDs ) {
2017-10-26 22:21:21 +03:00
$sOrderBySQL = 'ST_Distance(l.centroid, f.geometry)' ;
2017-10-07 13:01:56 +03:00
} 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 ) {
2017-10-26 22:21:21 +03:00
$sSQL .= ',placex as f WHERE ' ;
2017-10-07 13:01:56 +03:00
$sSQL .= " f.place_id in ( $sPlaceIDs ) " ;
$sSQL .= " AND ST_DWithin(l.centroid, f.centroid, $fRange ) " ;
} elseif ( $sPlaceGeom ) {
$sSQL .= " WHERE ST_Contains(' $sPlaceGeom ', l.centroid) " ;
}
2017-10-09 00:15:06 +03:00
$sSQL .= $this -> oContext -> excludeSQL ( ' AND l.place_id' );
2017-10-07 13:01:56 +03:00
$sSQL .= 'limit 300) i ' ;
if ( $sOrderBySQL ) {
$sSQL .= 'order by order_term asc' ;
}
$sSQL .= " limit $iLimit " ;
2018-03-24 19:44:13 +03:00
Debug :: printSQL ( $sSQL );
2017-10-07 13:01:56 +03:00
2019-03-10 17:42:58 +03:00
foreach ( $oDB -> getCol ( $sSQL ) as $iPlaceId ) {
2017-10-19 00:54:47 +03:00
$aResults [ $iPlaceId ] = new Result ( $iPlaceId );
}
2017-10-07 13:01:56 +03:00
} else {
2017-10-08 22:23:31 +03:00
if ( $this -> oContext -> hasNearPoint ()) {
$fRange = $this -> oContext -> nearRadius ();
2017-10-07 13:01:56 +03:00
}
$sOrderBySQL = '' ;
2017-10-08 22:23:31 +03:00
if ( $this -> oContext -> hasNearPoint ()) {
$sOrderBySQL = $this -> oContext -> distanceSQL ( 'l.geometry' );
2017-10-07 13:01:56 +03:00
} else {
2017-10-26 22:21:21 +03:00
$sOrderBySQL = 'ST_Distance(l.geometry, f.geometry)' ;
2017-10-07 13:01:56 +03:00
}
$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 . " ' " ;
2017-10-09 00:15:06 +03:00
$sSQL .= $this -> oContext -> excludeSQL ( ' AND l.place_id' );
2017-10-07 13:01:56 +03:00
if ( $sOrderBySQL ) {
2017-10-26 22:21:21 +03:00
$sSQL .= 'ORDER BY orderterm ASC' ;
2017-10-07 13:01:56 +03:00
}
$sSQL .= " limit $iLimit " ;
2018-03-24 19:44:13 +03:00
Debug :: printSQL ( $sSQL );
2017-10-07 13:01:56 +03:00
2019-03-10 17:42:58 +03:00
foreach ( $oDB -> getCol ( $sSQL ) as $iPlaceId ) {
2017-10-19 00:54:47 +03:00
$aResults [ $iPlaceId ] = new Result ( $iPlaceId );
}
2017-10-07 13:01:56 +03:00
}
}
}
2017-10-19 00:54:47 +03:00
return $aResults ;
2017-10-07 13:01:56 +03:00
}
2017-10-08 16:26:14 +03:00
2017-10-10 00:12:13 +03:00
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 '' ;
}
2017-10-08 16:26:14 +03:00
/////////// Sort functions
2017-10-08 18:00:59 +03:00
public static function bySearchRank ( $a , $b )
2017-10-08 16:26:14 +03:00
{
if ( $a -> iSearchRank == $b -> iSearchRank ) {
return $a -> iOperator + strlen ( $a -> sHouseNumber )
- $b -> iOperator - strlen ( $b -> sHouseNumber );
}
return $a -> iSearchRank < $b -> iSearchRank ? - 1 : 1 ;
}
2017-10-08 17:03:30 +03:00
//////////// Debugging functions
2017-10-08 18:00:59 +03:00
2018-03-24 19:44:13 +03:00
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 ,
2021-01-17 00:20:23 +03:00
'Address terms (full words)' => $this -> aFullNameAddress ? ? '' ,
2018-03-24 19:44:13 +03:00
'Special search' => $this -> iOperator ,
'Class' => $this -> sClass ,
'Type' => $this -> sType ,
'House number' => $this -> sHouseNumber ,
'Postcode' => $this -> sPostcode
);
}
2017-10-08 18:00:59 +03:00
public function dumpAsHtmlTableRow ( & $aWordIDs )
2017-10-08 17:03:30 +03:00
{
2017-10-08 18:00:59 +03:00
$kf = function ( $k ) use ( & $aWordIDs ) {
2021-01-17 00:20:23 +03:00
return $aWordIDs [ $k ] ? ? '[' . $k . ']' ;
2017-10-08 18:00:59 +03:00
};
2017-10-08 17:03:30 +03:00
2017-10-26 22:21:21 +03:00
echo '<tr>' ;
2017-10-08 17:03:30 +03:00
echo " <td> $this->iSearchRank </td> " ;
2017-10-26 22:21:21 +03:00
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>' ;
2017-10-08 17:03:30 +03:00
}
2017-10-08 18:00:59 +03:00
}