X-Git-Url: https://git.openstreetmap.org./nominatim.git/blobdiff_plain/9a5d5d9aec4cf785c8190c37a3136cf09aca6902..4e99f8a3408d59f5c6bf8d934fda7bb3726f2da2:/lib/SearchDescription.php?ds=sidebyside diff --git a/lib/SearchDescription.php b/lib/SearchDescription.php index f3afaff2..eba5f6a9 100644 --- a/lib/SearchDescription.php +++ b/lib/SearchDescription.php @@ -43,22 +43,55 @@ class SearchDescription 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 likelyhood that the + * search is a correct interpretation of the search query. + * + * @return integer Search rank. + */ public function getRank() { return $this->iSearchRank; } + /** + * Increase the search rank. + * + * @param integer $iAddRank Number of ranks to increase. + * + * @return void + */ public function addToRank($iAddRank) { $this->iSearchRank += $iAddRank; return $this->iSearchRank; } + /** + * 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; @@ -66,6 +99,11 @@ class SearchDescription $this->sType = $sType; } + /** + * Check if this might be a full address search. + * + * @return bool True if the search contains name, address and housenumber. + */ public function looksLikeFullAddress() { return sizeof($this->aName) @@ -73,28 +111,27 @@ class SearchDescription && preg_match('/[0-9]+/', $this->sHouseNumber); } - private function poiTable() - { - return 'place_classtype_'.$this->sClass.'_'.$this->sType; - } - - public function countryCodeSQL($sVar) - { - if ($this->sCountryCode) { - return $sVar.' = \''.$this->sCountryCode."'"; - } - if ($this->oContext->sqlCountryList) { - return $sVar.' in '.$this->oContext->sqlCountryList; - } - - return ''; - } - + /** + * Check if any operator is set. + * + * @return bool True, if this is a special search operation. + */ public function hasOperator() { return $this->iOperator != Operator::NONE; } + /** + * Extract key/value pairs from a query. + * + * Key/value pairs are recognised if they are of the form [=]. + * 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 [=]. @@ -115,18 +152,20 @@ class SearchDescription return $sQuery; } - public function isValidSearch(&$aCountryCodes) + /** + * Check if the combination of parameters is sensible. + * + * @return bool True, if the search looks valid. + */ + public function isValidSearch() { if (!sizeof($this->aName)) { if ($this->sHouseNumber) { return false; } - } - if ($aCountryCodes - && $this->sCountryCode - && !in_array($this->sCountryCode, $aCountryCodes) - ) { - return false; + if (!$this->sClass && !$this->sCountryCode) { + return false; + } } return true; @@ -135,7 +174,24 @@ class SearchDescription /////////// Search building functions - public function extendWithFullTerm($aSearchTerm, $bWordInQuery, $bHasPartial, $sPhraseType, $bFirstToken, $bFirstPhrase, $bLastToken, &$iGlobalRank) + /** + * Derive new searches by adding a full term to the existing search. + * + * @param mixed[] $aSearchTerm Description of the token. + * @param bool $bHasPartial True if there are also tokens of partial terms + * with the same name. + * @param string $sPhraseType Type of phrase the token is contained in. + * @param bool $bFirstToken True if the token is at the beginning of the + * query. + * @param bool $bFirstPhrase True if the token is in the first phrase of + * the query. + * @param bool $bLastToken True if the token is at the end of the query. + * @param integer $iGlobalRank Changable ranking of all searches in the + * batch. + * + * @return SearchDescription[] List of derived search descriptions. + */ + public function extendWithFullTerm($aSearchTerm, $bHasPartial, $sPhraseType, $bFirstToken, $bFirstPhrase, $bLastToken, &$iGlobalRank) { $aNewSearches = array(); @@ -166,7 +222,8 @@ class SearchDescription // We need to try the case where the postal code is the primary element // (i.e. no way to tell if it is (postalcode, city) OR (city, postalcode) // so try both. - if (!$this->sPostcode && $bWordInQuery + if (!$this->sPostcode + && $aSearchTerm['word'] && pg_escape_string($aSearchTerm['word']) == $aSearchTerm['word'] ) { // If we have structured search or this is the first term, @@ -215,16 +272,8 @@ class SearchDescription } $aNewSearches[] = $oSearch; } - } elseif ($sPhraseType == '' - && $aSearchTerm['class'] !== '' && $aSearchTerm['class'] !== null - ) { - // require a normalized exact match of the term - // if we have the normalizer version of the query - // available - if ($this->iOperator == Operator::NONE - && (isset($aSearchTerm['word']) && $aSearchTerm['word']) - && $bWordInQuery - ) { + } elseif ($sPhraseType == '' && $aSearchTerm['class']) { + if ($this->iOperator == Operator::NONE) { $oSearch = clone $this; $oSearch->iSearchRank++; @@ -239,7 +288,10 @@ class SearchDescription $oSearch->setPoiSearch($iOp, $aSearchTerm['class'], $aSearchTerm['type']); $aNewSearches[] = $oSearch; } - } elseif (isset($aSearchTerm['word_id']) && $aSearchTerm['word_id']) { + } elseif (isset($aSearchTerm['word_id']) + && $aSearchTerm['word_id'] + && $sPhraseType != 'country' + ) { $iWordID = $aSearchTerm['word_id']; if (sizeof($this->aName)) { if (($sPhraseType == '' || !$bFirstPhrase) @@ -264,7 +316,18 @@ class SearchDescription return $aNewSearches; } - public function extendWithPartialTerm($aSearchTerm, $bStructuredPhrases, $iPhrase, &$aWordFrequencyScores, $aFullTokens) + /** + * Derive new searches by adding a partial term to the existing search. + * + * @param mixed[] $aSearchTerm Description of the token. + * @param bool $bStructuredPhrases True if the search is structured. + * @param integer $iPhrase Number of the phrase the token is in. + * @param array[] $aFullTokens List of full term tokens with the + * same name. + * + * @return SearchDescription[] List of derived search descriptions. + */ + public function extendWithPartialTerm($aSearchTerm, $bStructuredPhrases, $iPhrase, $aFullTokens) { // Only allow name terms. if (!(isset($aSearchTerm['word_id']) && $aSearchTerm['word_id'])) { @@ -278,7 +341,7 @@ class SearchDescription && sizeof($this->aName) && strpos($aSearchTerm['word_token'], ' ') === false ) { - if ($aWordFrequencyScores[$iWordID] < CONST_Max_Word_Frequency) { + if ($aSearchTerm['search_name_count'] + 1 < CONST_Max_Word_Frequency) { $oSearch = clone $this; $oSearch->iSearchRank++; $oSearch->aAddress[$iWordID] = $iWordID; @@ -321,7 +384,7 @@ class SearchDescription if (preg_match('#^[0-9]+$#', $aSearchTerm['word_token'])) { $oSearch->iSearchRank += 2; } - if ($aWordFrequencyScores[$iWordID] < CONST_Max_Word_Frequency) { + if ($aSearchTerm['search_name_count'] + 1 < CONST_Max_Word_Frequency) { $oSearch->aName[$iWordID] = $iWordID; } else { $oSearch->aNameNonSearch[$iWordID] = $iWordID; @@ -335,6 +398,24 @@ class SearchDescription /////////// Query functions + + /** + * Query database for places that match this search. + * + * @param object $oDB Database connection to use. + * @param mixed[] $aWordFrequencyScores Number of times tokens appears + * overall in a planet database. + * @param mixed[] $aExactMatchCache Saves number of exact matches. + * @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 appicable or -1 if not. + */ public function query(&$oDB, &$aWordFrequencyScores, &$aExactMatchCache, $iMinRank, $iMaxRank, $iLimit) { $aPlaceIDs = array(); @@ -450,7 +531,7 @@ class SearchDescription } if ($this->oContext->hasNearPoint()) { $sSQL .= ' WHERE '.$this->oContext->withinSQL('ct.centroid'); - } else if ($this->oContext->bViewboxBounded) { + } elseif ($this->oContext->bViewboxBounded) { $sSQL .= ' WHERE ST_Contains('.$this->oContext->sqlViewboxSmall.', ct.centroid)'; } if ($this->oContext->sqlCountryList) { @@ -869,6 +950,22 @@ class SearchDescription return $aClassPlaceIDs; } + 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