<?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.'/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');
$aParams['exclude_place_ids'] = implode(',', $this->aExcludePlaceIDs);
}
- if ($this->bBoundedSearch) $aParams['bounded'] = '1';
+ if ($this->bBoundedSearch) {
+ $aParams['bounded'] = '1';
+ }
if ($this->aCountryCodes) {
$aParams['countrycodes'] = implode(',', $this->aCountryCodes);
public function setLimit($iLimit = 10)
{
- if ($iLimit > 50) $iLimit = 50;
- if ($iLimit < 1) $iLimit = 1;
+ if ($iLimit > 50) {
+ $iLimit = 50;
+ } elseif ($iLimit < 1) {
+ $iLimit = 1;
+ }
$this->iFinalLimit = $iLimit;
- $this->iLimit = $iLimit + min($iLimit, 10);
+ $this->iLimit = $iLimit + max($iLimit, 10);
}
public function setFeatureType($sFeatureType)
$this->bFallback = $oParams->getBool('fallback', $this->bFallback);
- // List of excluded Place IDs - used for more acurate pageing
+ // 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)
+ if ($iExcludedPlaceID) {
$aExcludePlaceIDs[$iExcludedPlaceID] = $iExcludedPlaceID;
+ }
}
- if (isset($aExcludePlaceIDs))
+ 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);
+ if (!$sFeatureType) {
+ $sFeatureType = $oParams->getString('featuretype');
+ }
+ if ($sFeatureType) {
+ $this->setFeatureType($sFeatureType);
+ }
// Country code list
$sCountries = $oParams->getStringList('countrycodes');
$aCountries[] = strtolower($sCountryCode);
}
}
- if (isset($aCountries))
+ if (isset($aCountries)) {
$this->aCountryCodes = $aCountries;
+ }
}
$aViewbox = $oParams->getStringList('viewboxlbrt');
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);
+ $this->setStructuredQuery(
+ $oParams->getString('amenity'),
+ $oParams->getString('street'),
+ $oParams->getString('city'),
+ $oParams->getString('county'),
+ $oParams->getString('state'),
+ $oParams->getString('country'),
+ $oParams->getString('postalcode')
+ );
+ if (!$this->sQuery) {
+ $sQuery = $oParams->getString('q');
+
+ if ($sQuery) {
+ $this->setQuery($sQuery);
+ }
}
}
public function loadStructuredAddressElement($sValue, $sKey, $iNewMinAddressRank, $iNewMaxAddressRank, $aItemListValues)
{
$sValue = trim($sValue);
- if (!$sValue) return false;
+ 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);
+ if ($aItemListValues) {
+ $this->aAddressRankList = array_merge($this->aAddressRankList, $aItemListValues);
+ }
return true;
}
{
$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\')';
+ if ($sAmenity || $sStreet || $sCity || $sCounty || $sState || $sCountry || $sPostalCode) {
+ // 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()
{
- if (!$this->aStructuredQuery) return false;
-
$aParams = $this->aStructuredQuery;
- if (count($aParams) == 1) return false;
+ if (!$aParams || count($aParams) == 1) {
+ return false;
+ }
$aOrderToFallback = array('postalcode', 'street', 'city', 'county', 'state');
*/
foreach ($aPhrases as $iPhrase => $oPhrase) {
$aNewPhraseSearches = array();
- $sPhraseType = $oPhrase->getPhraseType();
+ $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) {
- //echo "<br><b>$sToken</b>";
$aNewWordsetSearches = array();
+ $oPosition->setTokenPosition($iToken, count($aWordset));
foreach ($aWordsetSearches as $oCurrentSearch) {
- //echo "<i>";
- //var_dump($oCurrentSearch);
- //echo "</i>";
-
- // Tokens with full name matches.
- foreach ($oValidTokens->get(' '.$sToken) as $oSearchTerm) {
- $aNewSearches = $oCurrentSearch->extendWithFullTerm(
- $oSearchTerm,
- $oValidTokens->contains($sToken)
- && strpos($sToken, ' ') === false,
- $sPhraseType,
- $iToken == 0 && $iPhrase == 0,
- $iPhrase == 0,
- $iToken + 1 == count($aWordset)
- && $iPhrase + 1 == count($aPhrases)
- );
-
- foreach ($aNewSearches as $oSearch) {
- if ($oSearch->getRank() < $this->iMaxRank) {
- $aNewWordsetSearches[] = $oSearch;
- }
- }
- }
- // Look for partial matches.
- // Note that there is no point in adding country terms here
- // because country is omitted in the address.
- if ($sPhraseType != 'country') {
- // Allow searching for a word - but at extra cost
- foreach ($oValidTokens->get($sToken) as $oSearchTerm) {
- $aNewSearches = $oCurrentSearch->extendWithPartialTerm(
- $sToken,
- $oSearchTerm,
- (bool) $sPhraseType,
- $iPhrase,
- $oValidTokens->get(' '.$sToken)
+ foreach ($oValidTokens->get($sToken) as $oSearchTerm) {
+ if ($oSearchTerm->isExtendable($oCurrentSearch, $oPosition)) {
+ $aNewSearches = $oSearchTerm->extendSearch(
+ $oCurrentSearch,
+ $oPosition
);
foreach ($aNewSearches as $oSearch) {
usort($aNewWordsetSearches, array('Nominatim\SearchDescription', 'bySearchRank'));
$aWordsetSearches = array_slice($aNewWordsetSearches, 0, 50);
}
- //var_Dump('<hr>',count($aWordsetSearches)); exit;
$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;
+ if (isset($aSearchHash[$sHash])) {
+ unset($aNewPhraseSearches[$iSearch]);
+ } else {
+ $aSearchHash[$sHash] = 1;
+ }
}
$aNewPhraseSearches = array_slice($aNewPhraseSearches, 0, 50);
$iSearchCount = 0;
$aSearches = array();
- foreach ($aGroupedSearches as $iScore => $aNewSearches) {
+ foreach ($aGroupedSearches as $aNewSearches) {
$iSearchCount += count($aNewSearches);
$aSearches = array_merge($aSearches, $aNewSearches);
- if ($iSearchCount > 50) break;
+ if ($iSearchCount > 50) {
+ break;
+ }
}
}
public function lookup()
{
Debug::newFunction('Geocode::lookup');
- if (!$this->sQuery && !$this->aStructuredQuery) return array();
+ if (!$this->sQuery && !$this->aStructuredQuery) {
+ return array();
+ }
Debug::printDebugArray('Geocode', $this);
if ($this->aCountryCodes) {
$oCtx->setCountryList($this->aCountryCodes);
}
- $this->oTokenizer->setCountryRestriction($this->aCountryCodes);
Debug::newSection('Query Preprocessing');
- $sLanguagePrefArraySQL = $this->oDB->getArraySQL(
- $this->oDB->getDBQuotedList($this->aLangPrefOrder)
- );
-
$sQuery = $this->sQuery;
if (!preg_match('//u', $sQuery)) {
userError('Query string is not UTF-8 encoded.');
}
- // Conflicts between US state abreviations and various words for 'the' in different languages
- if (isset($this->aLangPrefOrder['name:en'])) {
- $sQuery = preg_replace('/(^|,)\s*il\s*(,|$)/i', '\1illinois\2', $sQuery);
- $sQuery = preg_replace('/(^|,)\s*al\s*(,|$)/i', '\1alabama\2', $sQuery);
- $sQuery = preg_replace('/(^|,)\s*la\s*(,|$)/i', '\1louisiana\2', $sQuery);
- }
-
// Do we have anything that looks like a lat/lon pair?
$sQuery = $oCtx->setNearPointFromQuery($sQuery);
if (!empty($aTokens)) {
$aNewSearches = array();
+ $oPosition = new SearchPosition('', 0, 1);
+ $oPosition->setTokenPosition(0, 1);
+
foreach ($aSearches as $oSearch) {
foreach ($aTokens as $oToken) {
- $oNewSearch = clone $oSearch;
- $oNewSearch->setPoiSearch(
- $oToken->iOperator,
- $oToken->sClass,
- $oToken->sType
+ $aNewSearches = array_merge(
+ $aNewSearches,
+ $oToken->extendSearch($oSearch, $oPosition)
);
- $aNewSearches[] = $oNewSearch;
}
}
$aSearches = $aNewSearches;
}
$aReverseGroupedSearches = $this->getGroupedSearches($aSearches, $aPhrases, $oValidTokens);
- foreach ($aGroupedSearches as $aSearches) {
+ foreach ($aReverseGroupedSearches as $aSearches) {
foreach ($aSearches as $aSearch) {
- if (!isset($aReverseGroupedSearches[$aSearch->getRank()])) {
- $aReverseGroupedSearches[$aSearch->getRank()] = array();
+ if (!isset($aGroupedSearches[$aSearch->getRank()])) {
+ $aGroupedSearches[$aSearch->getRank()] = array();
}
- $aReverseGroupedSearches[$aSearch->getRank()][] = $aSearch;
+ $aGroupedSearches[$aSearch->getRank()][] = $aSearch;
}
}
- $aGroupedSearches = $aReverseGroupedSearches;
ksort($aGroupedSearches);
}
} else {
$aGroupedSearches = array();
foreach ($aSearches as $aSearch) {
if ($aSearch->getRank() < $this->iMaxRank) {
- if (!isset($aGroupedSearches[$aSearch->getRank()])) $aGroupedSearches[$aSearch->getRank()] = array();
+ if (!isset($aGroupedSearches[$aSearch->getRank()])) {
+ $aGroupedSearches[$aSearch->getRank()] = array();
+ }
$aGroupedSearches[$aSearch->getRank()][] = $aSearch;
}
}
$sHash = serialize($aSearch);
if (isset($aSearchHash[$sHash])) {
unset($aGroupedSearches[$iGroup][$iSearch]);
- if (empty($aGroupedSearches[$iGroup])) unset($aGroupedSearches[$iGroup]);
+ if (empty($aGroupedSearches[$iGroup])) {
+ unset($aGroupedSearches[$iGroup]);
+ }
} else {
$aSearchHash[$sHash] = 1;
}
}
}
- if ($iQueryLoop > 20) break;
+ if ($iQueryLoop > 30) {
+ break;
+ }
}
if (!empty($aResults)) {
$aResults = $tempIDs;
}
- if (!empty($aResults)) break;
- if ($iGroupLoop > 4) break;
- if ($iQueryLoop > 30) break;
+ if (!empty($aResults) || $iGroupLoop > 6 || $iQueryLoop > 40) {
+ break;
+ }
}
} else {
// Just interpret as a reverse geocode
// No results? Done
if (empty($aResults)) {
- if ($this->bFallback) {
- if ($this->fallbackStructuredQuery()) {
- return $this->lookup();
- }
+ if ($this->bFallback && $this->fallbackStructuredQuery()) {
+ return $this->lookup();
}
return array();
$aRecheckWords = preg_split('/\b[\s,\\-]*/u', $sQuery);
foreach ($aRecheckWords as $i => $sWord) {
- if (!preg_match('/[\pL\pN]/', $sWord)) unset($aRecheckWords[$i]);
+ if (!preg_match('/[\pL\pN]/', $sWord)) {
+ unset($aRecheckWords[$i]);
+ }
}
Debug::printVar('Recheck words', $aRecheckWords);
$aResult['importance'] = 0.001;
$aResult['foundorder'] = $aResult['addressimportance'];
} else {
- $aResult['importance'] = max(0.001, $aResult['importance']);
+ if ($aResult['importance'] == 0) {
+ $aResult['importance'] = 0.0001;
+ }
$aResult['importance'] *= $this->viewboxImportanceFactor(
$aResult['lon'],
$aResult['lat']
$iCountWords = 0;
$sAddress = $aResult['langaddress'];
foreach ($aRecheckWords as $i => $sWord) {
- if (stripos($sAddress, $sWord)!==false) {
+ if (grapheme_stripos($sAddress, $sWord)!==false) {
$iCountWords++;
- if (preg_match('/(^|,)\s*'.preg_quote($sWord, '/').'\s*(,|$)/', $sAddress)) $iCountWords += 0.1;
+ if (preg_match('/(^|,)\s*'.preg_quote($sWord, '/').'\s*(,|$)/', $sAddress)) {
+ $iCountWords += 0.1;
+ }
}
}
$aToFilter = $aSearchResults;
$aSearchResults = array();
- $bFirst = true;
foreach ($aToFilter as $aResult) {
$this->aExcludePlaceIDs[$aResult['place_id']] = $aResult['place_id'];
- if ($bFirst) {
- $fLat = $aResult['lat'];
- $fLon = $aResult['lon'];
- if (isset($aResult['zoom'])) $iZoom = $aResult['zoom'];
- $bFirst = false;
- }
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']]))
) {
}
// Absolute limit on number of results
- if (count($aSearchResults) >= $this->iFinalLimit) break;
+ if (count($aSearchResults) >= $this->iFinalLimit) {
+ break;
+ }
}
Debug::printVar('Post-filter results', $aSearchResults);