2013-08-27 20:08:57 +04:00
< ? php
2016-09-16 03:27:36 +03:00
namespace Nominatim ;
2016-09-04 04:19:48 +03:00
require_once ( CONST_BasePath . '/lib/PlaceLookup.php' );
2017-10-12 23:37:44 +03:00
require_once ( CONST_BasePath . '/lib/Phrase.php' );
2016-09-04 04:19:48 +03:00
require_once ( CONST_BasePath . '/lib/ReverseGeocode.php' );
2017-10-08 16:26:14 +03:00
require_once ( CONST_BasePath . '/lib/SearchDescription.php' );
2017-10-08 22:23:31 +03:00
require_once ( CONST_BasePath . '/lib/SearchContext.php' );
2016-09-04 04:19:48 +03:00
class Geocode
{
protected $oDB ;
2017-10-24 00:28:00 +03:00
protected $oPlaceLookup ;
2016-09-04 04:19:48 +03:00
protected $aLangPrefOrder = array ();
protected $bIncludeAddressDetails = false ;
protected $aExcludePlaceIDs = array ();
protected $bReverseInPlan = false ;
protected $iLimit = 20 ;
protected $iFinalLimit = 10 ;
protected $iOffset = 0 ;
protected $bFallback = false ;
protected $aCountryCodes = false ;
protected $bBoundedSearch = false ;
protected $aViewBox = false ;
2017-10-08 23:44:01 +03:00
protected $aRoutePoints = false ;
protected $aRouteWidth = false ;
2016-09-04 04:19:48 +03:00
protected $iMaxRank = 20 ;
protected $iMinAddressRank = 0 ;
protected $iMaxAddressRank = 30 ;
protected $aAddressRankList = array ();
protected $sAllowedTypesSQLList = false ;
protected $sQuery = false ;
protected $aStructuredQuery = false ;
2017-07-07 00:06:13 +03:00
protected $oNormalizer = null ;
2016-09-14 04:16:46 +03:00
2016-09-16 03:27:36 +03:00
public function __construct ( & $oDB )
2016-09-04 04:19:48 +03:00
{
$this -> oDB =& $oDB ;
2017-10-24 00:28:00 +03:00
$this -> oPlaceLookup = new PlaceLookup ( $this -> oDB );
2017-07-07 00:06:13 +03:00
$this -> oNormalizer = \Transliterator :: createFromRules ( CONST_Term_Normalization_Rules );
}
private function normTerm ( $sTerm )
{
if ( $this -> oNormalizer === null ) {
2017-09-30 10:39:47 +03:00
return $sTerm ;
2017-07-07 00:06:13 +03:00
}
return $this -> oNormalizer -> transliterate ( $sTerm );
2016-09-04 04:19:48 +03:00
}
2016-09-16 03:27:36 +03:00
public function setReverseInPlan ( $bReverse )
2016-09-04 04:19:48 +03:00
{
$this -> bReverseInPlan = $bReverse ;
}
2016-09-16 03:27:36 +03:00
public function setLanguagePreference ( $aLangPref )
2016-09-04 04:19:48 +03:00
{
$this -> aLangPrefOrder = $aLangPref ;
}
2017-03-23 02:16:58 +03:00
public function getMoreUrlParams ()
2016-09-04 04:19:48 +03:00
{
2017-03-23 02:16:58 +03:00
if ( $this -> aStructuredQuery ) {
$aParams = $this -> aStructuredQuery ;
} else {
$aParams = array ( 'q' => $this -> sQuery );
}
2016-09-04 04:19:48 +03:00
2017-10-24 00:28:00 +03:00
$aParams = array_merge ( $aParams , $this -> oPlaceLookup -> getMoreUrlParams ());
2017-03-23 02:16:58 +03:00
if ( $this -> aExcludePlaceIDs ) {
$aParams [ 'exclude_place_ids' ] = implode ( ',' , $this -> aExcludePlaceIDs );
}
2016-09-04 04:19:48 +03:00
2017-03-23 02:16:58 +03:00
if ( $this -> bIncludeAddressDetails ) $aParams [ 'addressdetails' ] = '1' ;
if ( $this -> bBoundedSearch ) $aParams [ 'bounded' ] = '1' ;
if ( $this -> aCountryCodes ) {
$aParams [ 'countrycodes' ] = implode ( ',' , $this -> aCountryCodes );
}
if ( $this -> aViewBox ) {
2017-10-22 22:59:08 +03:00
$aParams [ 'viewbox' ] = join ( ',' , $this -> aViewBox );
2017-03-23 02:16:58 +03:00
}
return $aParams ;
2016-09-04 04:19:48 +03:00
}
2016-09-16 03:27:36 +03:00
public function setLimit ( $iLimit = 10 )
2016-09-04 04:19:48 +03:00
{
if ( $iLimit > 50 ) $iLimit = 50 ;
if ( $iLimit < 1 ) $iLimit = 1 ;
$this -> iFinalLimit = $iLimit ;
$this -> iLimit = $iLimit + min ( $iLimit , 10 );
}
2016-09-16 03:27:36 +03:00
public function setFeatureType ( $sFeatureType )
2016-09-04 04:19:48 +03:00
{
2016-09-08 04:16:22 +03:00
switch ( $sFeatureType ) {
2016-09-14 04:16:46 +03:00
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 ;
2016-09-04 04:19:48 +03:00
}
}
2016-09-16 03:27:36 +03:00
public function setRankRange ( $iMin , $iMax )
2016-09-04 04:19:48 +03:00
{
$this -> iMinAddressRank = $iMin ;
$this -> iMaxAddressRank = $iMax ;
}
2016-09-16 03:27:36 +03:00
public function setViewbox ( $aViewbox )
2016-09-04 04:19:48 +03:00
{
2017-10-22 22:59:08 +03:00
$aBox = array_map ( 'floatval' , $aViewbox );
2016-09-04 04:19:48 +03:00
2017-10-22 22:59:08 +03:00
$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 ]));
2016-10-13 09:03:28 +03:00
2017-10-22 22:59:08 +03:00
if ( $this -> aViewBox [ 2 ] - $this -> aViewBox [ 0 ] < 0.000000001
|| $this -> aViewBox [ 3 ] - $this -> aViewBox [ 1 ] < 0.000000001
2016-10-12 23:13:07 +03:00
) {
2016-10-13 09:03:28 +03:00
userError ( " Bad parameter 'viewbox'. Not a box. " );
2016-10-12 23:13:07 +03:00
}
2016-09-04 04:19:48 +03:00
}
2017-10-22 22:59:08 +03:00
private function viewboxImportanceFactor ( $fX , $fY )
{
$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 ;
}
2016-09-16 03:27:36 +03:00
public function setQuery ( $sQueryString )
2016-09-04 04:19:48 +03:00
{
$this -> sQuery = $sQueryString ;
$this -> aStructuredQuery = false ;
}
2016-09-16 03:27:36 +03:00
public function getQueryString ()
2016-09-04 04:19:48 +03:00
{
return $this -> sQuery ;
}
2017-10-24 00:28:00 +03:00
public function loadParamArray ( $oParams , $sForceGeometryType = null )
2016-09-04 04:19:48 +03:00
{
2016-09-11 06:22:51 +03:00
$this -> bIncludeAddressDetails
= $oParams -> getBool ( 'addressdetails' , $this -> bIncludeAddressDetails );
2016-09-04 04:19:48 +03:00
$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 acurate pageing
$sExcluded = $oParams -> getStringList ( 'exclude_place_ids' );
2016-09-08 04:16:22 +03:00
if ( $sExcluded ) {
foreach ( $sExcluded as $iExcludedPlaceID ) {
2016-09-04 04:19:48 +03:00
$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' );
2016-09-08 04:16:22 +03:00
if ( $sCountries ) {
foreach ( $sCountries as $sCountryCode ) {
if ( preg_match ( '/^[a-zA-Z][a-zA-Z]$/' , $sCountryCode )) {
2016-09-04 04:19:48 +03:00
$aCountries [] = strtolower ( $sCountryCode );
}
}
2016-10-04 21:32:22 +03:00
if ( isset ( $aCountries ))
2016-09-04 04:19:48 +03:00
$this -> aCountryCodes = $aCountries ;
}
$aViewbox = $oParams -> getStringList ( 'viewboxlbrt' );
2016-09-08 04:16:22 +03:00
if ( $aViewbox ) {
2016-10-12 23:13:07 +03:00
if ( count ( $aViewbox ) != 4 ) {
2017-09-18 00:30:08 +03:00
userError ( " Bad parmater 'viewboxlbrt'. Expected 4 coordinates. " );
2016-10-12 23:13:07 +03:00
}
2016-09-04 04:19:48 +03:00
$this -> setViewbox ( $aViewbox );
2016-09-08 04:16:22 +03:00
} else {
2016-09-04 04:19:48 +03:00
$aViewbox = $oParams -> getStringList ( 'viewbox' );
2016-09-08 04:16:22 +03:00
if ( $aViewbox ) {
2016-10-12 23:13:07 +03:00
if ( count ( $aViewbox ) != 4 ) {
userError ( " Bad parmater 'viewbox'. Expected 4 coordinates. " );
}
2017-09-30 15:16:07 +03:00
$this -> setViewBox ( $aViewbox );
2016-09-08 04:16:22 +03:00
} else {
2016-09-04 04:19:48 +03:00
$aRoute = $oParams -> getStringList ( 'route' );
$fRouteWidth = $oParams -> getFloat ( 'routewidth' );
2016-09-08 04:16:22 +03:00
if ( $aRoute && $fRouteWidth ) {
2017-10-08 23:44:01 +03:00
$this -> aRoutePoints = $aRoute ;
$this -> aRouteWidth = $fRouteWidth ;
2016-09-04 04:19:48 +03:00
}
}
}
2017-10-24 00:28:00 +03:00
$this -> oPlaceLookup -> loadParamArray ( $oParams , $sForceGeometryType );
$this -> oPlaceLookup -> setIncludeAddressDetails ( false );
$this -> oPlaceLookup -> setIncludePolygonAsPoints ( $oParams -> getBool ( 'polygon' ));
2016-09-04 04:19:48 +03:00
}
2016-09-16 03:27:36 +03:00
public function setQueryFromParams ( $oParams )
2016-09-04 04:19:48 +03:00
{
// Search query
$sQuery = $oParams -> getString ( 'q' );
2016-09-08 04:16:22 +03:00
if ( ! $sQuery ) {
2016-09-11 06:22:51 +03:00
$this -> setStructuredQuery (
$oParams -> getString ( 'amenity' ),
$oParams -> getString ( 'street' ),
$oParams -> getString ( 'city' ),
$oParams -> getString ( 'county' ),
$oParams -> getString ( 'state' ),
$oParams -> getString ( 'country' ),
$oParams -> getString ( 'postalcode' )
);
2016-09-04 04:19:48 +03:00
$this -> setReverseInPlan ( false );
2016-09-08 04:16:22 +03:00
} else {
2016-09-04 04:19:48 +03:00
$this -> setQuery ( $sQuery );
}
}
2016-09-16 03:27:36 +03:00
public function loadStructuredAddressElement ( $sValue , $sKey , $iNewMinAddressRank , $iNewMaxAddressRank , $aItemListValues )
2016-09-04 04:19:48 +03:00
{
$sValue = trim ( $sValue );
if ( ! $sValue ) return false ;
$this -> aStructuredQuery [ $sKey ] = $sValue ;
2016-09-08 04:16:22 +03:00
if ( $this -> iMinAddressRank == 0 && $this -> iMaxAddressRank == 30 ) {
2016-09-04 04:19:48 +03:00
$this -> iMinAddressRank = $iNewMinAddressRank ;
$this -> iMaxAddressRank = $iNewMaxAddressRank ;
}
if ( $aItemListValues ) $this -> aAddressRankList = array_merge ( $this -> aAddressRankList , $aItemListValues );
return true ;
}
2017-03-23 02:16:58 +03:00
public function setStructuredQuery ( $sAmenity = false , $sStreet = false , $sCity = false , $sCounty = false , $sState = false , $sCountry = false , $sPostalCode = false )
2016-09-04 04:19:48 +03:00
{
$this -> sQuery = false ;
// Reset
$this -> iMinAddressRank = 0 ;
$this -> iMaxAddressRank = 30 ;
$this -> aAddressRankList = array ();
$this -> aStructuredQuery = array ();
2017-10-08 18:00:59 +03:00
$this -> sAllowedTypesSQLList = false ;
2016-09-04 04:19:48 +03:00
2017-03-23 02:16:58 +03:00
$this -> loadStructuredAddressElement ( $sAmenity , 'amenity' , 26 , 30 , false );
2016-09-04 04:19:48 +03:00
$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 );
2016-09-11 06:22:51 +03:00
$this -> loadStructuredAddressElement ( $sPostalCode , 'postalcode' , 5 , 11 , array ( 5 , 11 ));
2016-09-04 04:19:48 +03:00
$this -> loadStructuredAddressElement ( $sCountry , 'country' , 4 , 4 , false );
2016-09-08 04:16:22 +03:00
if ( sizeof ( $this -> aStructuredQuery ) > 0 ) {
2016-09-04 04:19:48 +03:00
$this -> sQuery = join ( ', ' , $this -> aStructuredQuery );
2016-09-08 04:16:22 +03:00
if ( $this -> iMaxAddressRank < 30 ) {
2017-09-18 00:30:08 +03:00
$this -> sAllowedTypesSQLList = '(\'place\',\'boundary\')' ;
2016-09-04 04:19:48 +03:00
}
}
}
2016-09-16 03:27:36 +03:00
public function fallbackStructuredQuery ()
2016-09-04 04:19:48 +03:00
{
if ( ! $this -> aStructuredQuery ) return false ;
$aParams = $this -> aStructuredQuery ;
if ( sizeof ( $aParams ) == 1 ) return false ;
$aOrderToFallback = array ( 'postalcode' , 'street' , 'city' , 'county' , 'state' );
2016-09-08 04:16:22 +03:00
foreach ( $aOrderToFallback as $sType ) {
if ( isset ( $aParams [ $sType ])) {
2016-09-04 04:19:48 +03:00
unset ( $aParams [ $sType ]);
$this -> setStructuredQuery ( @ $aParams [ 'amenity' ], @ $aParams [ 'street' ], @ $aParams [ 'city' ], @ $aParams [ 'county' ], @ $aParams [ 'state' ], @ $aParams [ 'country' ], @ $aParams [ 'postalcode' ]);
return true ;
}
}
return false ;
}
2017-10-14 00:04:12 +03:00
public function getGroupedSearches ( $aSearches , $aPhrases , $aValidTokens , $bIsStructured )
2016-09-04 04:19:48 +03:00
{
/*
Calculate all searches using aValidTokens 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
*/
2017-10-12 23:37:44 +03:00
foreach ( $aPhrases as $iPhrase => $oPhrase ) {
2016-09-04 04:19:48 +03:00
$aNewPhraseSearches = array ();
2017-10-12 23:37:44 +03:00
$sPhraseType = $bIsStructured ? $oPhrase -> getPhraseType () : '' ;
2016-09-04 04:19:48 +03:00
2017-10-12 23:37:44 +03:00
foreach ( $oPhrase -> getWordSets () as $iWordSet => $aWordset ) {
2016-09-04 04:19:48 +03:00
// Too many permutations - too expensive
if ( $iWordSet > 120 ) break ;
$aWordsetSearches = $aSearches ;
// Add all words from this wordset
2016-09-08 04:16:22 +03:00
foreach ( $aWordset as $iToken => $sToken ) {
2016-09-04 04:19:48 +03:00
//echo "<br><b>$sToken</b>";
$aNewWordsetSearches = array ();
2017-10-08 13:57:22 +03:00
foreach ( $aWordsetSearches as $oCurrentSearch ) {
2016-09-04 04:19:48 +03:00
//echo "<i>";
2017-10-08 13:57:22 +03:00
//var_dump($oCurrentSearch);
2016-09-04 04:19:48 +03:00
//echo "</i>";
// If the token is valid
2016-09-08 04:16:22 +03:00
if ( isset ( $aValidTokens [ ' ' . $sToken ])) {
foreach ( $aValidTokens [ ' ' . $sToken ] as $aSearchTerm ) {
2017-10-08 13:57:22 +03:00
$aNewSearches = $oCurrentSearch -> extendWithFullTerm (
$aSearchTerm ,
isset ( $aValidTokens [ $sToken ])
&& strpos ( $sToken , ' ' ) === false ,
$sPhraseType ,
$iToken == 0 && $iPhrase == 0 ,
$iPhrase == 0 ,
$iToken + 1 == sizeof ( $aWordset )
2017-10-24 23:42:29 +03:00
&& $iPhrase + 1 == sizeof ( $aPhrases )
2017-10-08 13:57:22 +03:00
);
foreach ( $aNewSearches as $oSearch ) {
if ( $oSearch -> getRank () < $this -> iMaxRank ) {
$aNewWordsetSearches [] = $oSearch ;
2016-09-04 04:19:48 +03:00
}
}
}
}
// Look for partial matches.
// Note that there is no point in adding country terms here
2017-10-08 13:57:22 +03:00
// because country is omitted in the address.
2016-09-08 04:16:22 +03:00
if ( isset ( $aValidTokens [ $sToken ]) && $sPhraseType != 'country' ) {
2016-09-04 04:19:48 +03:00
// Allow searching for a word - but at extra cost
2016-09-08 04:16:22 +03:00
foreach ( $aValidTokens [ $sToken ] as $aSearchTerm ) {
2017-10-08 13:57:22 +03:00
$aNewSearches = $oCurrentSearch -> extendWithPartialTerm (
$aSearchTerm ,
2017-10-12 23:37:44 +03:00
$bIsStructured ,
2017-10-08 13:57:22 +03:00
$iPhrase ,
isset ( $aValidTokens [ ' ' . $sToken ]) ? $aValidTokens [ ' ' . $sToken ] : array ()
);
foreach ( $aNewSearches as $oSearch ) {
if ( $oSearch -> getRank () < $this -> iMaxRank ) {
$aNewWordsetSearches [] = $oSearch ;
2016-09-04 04:19:48 +03:00
}
}
}
}
}
// Sort and cut
2017-10-08 16:26:14 +03:00
usort ( $aNewWordsetSearches , array ( 'Nominatim\SearchDescription' , 'bySearchRank' ));
2016-09-04 04:19:48 +03:00
$aWordsetSearches = array_slice ( $aNewWordsetSearches , 0 , 50 );
}
//var_Dump('<hr>',sizeof($aWordsetSearches)); exit;
$aNewPhraseSearches = array_merge ( $aNewPhraseSearches , $aNewWordsetSearches );
2017-10-08 16:26:14 +03:00
usort ( $aNewPhraseSearches , array ( 'Nominatim\SearchDescription' , 'bySearchRank' ));
2016-09-04 04:19:48 +03:00
$aSearchHash = array ();
2016-09-08 04:16:22 +03:00
foreach ( $aNewPhraseSearches as $iSearch => $aSearch ) {
2016-09-04 04:19:48 +03:00
$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 ();
2016-09-08 04:16:22 +03:00
foreach ( $aNewPhraseSearches as $aSearch ) {
2017-10-08 13:57:22 +03:00
$iRank = $aSearch -> getRank ();
if ( $iRank < $this -> iMaxRank ) {
if ( ! isset ( $aGroupedSearches [ $iRank ])) {
$aGroupedSearches [ $iRank ] = array ();
}
$aGroupedSearches [ $iRank ][] = $aSearch ;
2016-09-04 04:19:48 +03:00
}
}
ksort ( $aGroupedSearches );
$iSearchCount = 0 ;
$aSearches = array ();
2016-09-08 04:16:22 +03:00
foreach ( $aGroupedSearches as $iScore => $aNewSearches ) {
2016-09-04 04:19:48 +03:00
$iSearchCount += sizeof ( $aNewSearches );
$aSearches = array_merge ( $aSearches , $aNewSearches );
if ( $iSearchCount > 50 ) break ;
}
//if (CONST_Debug) _debugDumpGroupedSearches($aGroupedSearches, $aValidTokens);
}
2017-10-02 22:57:44 +03:00
2017-10-07 13:23:46 +03:00
// Revisit searches, drop bad searches and give penalty to unlikely combinations.
2017-10-02 22:57:44 +03:00
$aGroupedSearches = array ();
2017-10-08 13:57:22 +03:00
foreach ( $aSearches as $oSearch ) {
2017-10-13 23:23:39 +03:00
if ( ! $oSearch -> isValidSearch ()) {
2017-10-07 13:23:46 +03:00
continue ;
}
2017-10-25 00:17:47 +03:00
$iRank = $oSearch -> getRank ();
2017-10-08 16:26:14 +03:00
if ( ! isset ( $aGroupedSearches [ $iRank ])) {
2017-10-08 13:57:22 +03:00
$aGroupedSearches [ $iRank ] = array ();
}
$aGroupedSearches [ $iRank ][] = $oSearch ;
2017-10-02 22:57:44 +03:00
}
ksort ( $aGroupedSearches );
2016-09-04 04:19:48 +03:00
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 http :// wiki . openstreetmap . org / wiki / Admin_level
rank_search : rank in search hierarchy
( see also http :// 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
*/
2016-09-14 04:16:46 +03:00
2016-09-16 03:27:36 +03:00
public function lookup ()
2016-09-04 04:19:48 +03:00
{
2017-03-23 01:02:19 +03:00
if ( ! $this -> sQuery && ! $this -> aStructuredQuery ) return array ();
2016-09-04 04:19:48 +03:00
2017-10-08 22:23:31 +03:00
$oCtx = new SearchContext ();
2017-10-08 23:44:01 +03:00
if ( $this -> aRoutePoints ) {
$oCtx -> setViewboxFromRoute (
$this -> oDB ,
$this -> aRoutePoints ,
$this -> aRouteWidth ,
$this -> bBoundedSearch
);
2017-10-10 00:12:13 +03:00
} elseif ( $this -> aViewBox ) {
2017-10-08 23:44:01 +03:00
$oCtx -> setViewboxFromBox ( $this -> aViewBox , $this -> bBoundedSearch );
}
2017-10-09 00:15:06 +03:00
if ( $this -> aExcludePlaceIDs ) {
$oCtx -> setExcludeList ( $this -> aExcludePlaceIDs );
}
2017-10-09 00:33:54 +03:00
if ( $this -> aCountryCodes ) {
$oCtx -> setCountryList ( $this -> aCountryCodes );
}
2017-10-08 23:44:01 +03:00
2017-07-07 00:06:13 +03:00
$sNormQuery = $this -> normTerm ( $this -> sQuery );
2017-10-08 11:06:17 +03:00
$sLanguagePrefArraySQL = getArraySQL (
2017-10-26 22:21:21 +03:00
array_map ( 'getDBQuoted' , $this -> aLangPrefOrder )
2017-10-08 11:06:17 +03:00
);
2016-09-04 04:19:48 +03:00
$sQuery = $this -> sQuery ;
2016-10-12 23:25:04 +03:00
if ( ! preg_match ( '//u' , $sQuery )) {
2017-10-26 22:21:21 +03:00
userError ( 'Query string is not UTF-8 encoded.' );
2016-10-12 23:25:04 +03:00
}
2016-09-04 04:19:48 +03:00
// Conflicts between US state abreviations and various words for 'the' in different languages
2016-09-08 04:16:22 +03:00
if ( isset ( $this -> aLangPrefOrder [ 'name:en' ])) {
2016-09-11 06:22:51 +03:00
$sQuery = preg_replace ( '/(^|,)\s*il\s*(,|$)/' , '\1illinois\2' , $sQuery );
$sQuery = preg_replace ( '/(^|,)\s*al\s*(,|$)/' , '\1alabama\2' , $sQuery );
$sQuery = preg_replace ( '/(^|,)\s*la\s*(,|$)/' , '\1louisiana\2' , $sQuery );
2016-09-04 04:19:48 +03:00
}
// Do we have anything that looks like a lat/lon pair?
2017-10-08 22:23:31 +03:00
$sQuery = $oCtx -> setNearPointFromQuery ( $sQuery );
2016-09-04 04:19:48 +03:00
2017-10-22 22:59:08 +03:00
$aResults = array ();
2016-09-08 04:16:22 +03:00
if ( $sQuery || $this -> aStructuredQuery ) {
2017-10-06 01:14:48 +03:00
// Start with a single blank search
2017-10-08 22:23:31 +03:00
$aSearches = array ( new SearchDescription ( $oCtx ));
2016-09-04 04:19:48 +03:00
2017-10-06 01:14:48 +03:00
if ( $sQuery ) {
$sQuery = $aSearches [ 0 ] -> extractKeyValuePairs ( $sQuery );
2016-09-04 04:19:48 +03:00
}
2016-09-08 04:16:22 +03:00
2017-10-06 01:14:48 +03:00
$sSpecialTerm = '' ;
if ( $sQuery ) {
preg_match_all (
'/\\[([\\w ]*)\\]/u' ,
$sQuery ,
$aSpecialTermsRaw ,
PREG_SET_ORDER
);
foreach ( $aSpecialTermsRaw as $aSpecialTerm ) {
$sQuery = str_replace ( $aSpecialTerm [ 0 ], ' ' , $sQuery );
if ( ! $sSpecialTerm ) {
$sSpecialTerm = $aSpecialTerm [ 1 ];
}
2017-09-18 23:07:08 +03:00
}
2017-10-06 01:14:48 +03:00
}
if ( ! $sSpecialTerm && $this -> aStructuredQuery
&& isset ( $this -> aStructuredQuery [ 'amenity' ])) {
$sSpecialTerm = $this -> aStructuredQuery [ 'amenity' ];
unset ( $this -> aStructuredQuery [ 'amenity' ]);
}
2017-09-18 23:07:08 +03:00
2017-10-06 01:14:48 +03:00
if ( $sSpecialTerm && ! $aSearches [ 0 ] -> hasOperator ()) {
$sSpecialTerm = pg_escape_string ( $sSpecialTerm );
$sToken = chksql (
$this -> oDB -> getOne ( " SELECT make_standard_name(' $sSpecialTerm ') " ),
2017-10-26 22:21:21 +03:00
'Cannot decode query. Wrong encoding?'
2017-10-06 01:14:48 +03:00
);
$sSQL = 'SELECT class, type FROM word ' ;
2016-10-29 19:49:21 +03:00
$sSQL .= ' WHERE word_token in (\' ' . $sToken . '\')' ;
2017-10-06 01:14:48 +03:00
$sSQL .= ' AND class is not null AND class not in (\'place\')' ;
2016-09-04 04:19:48 +03:00
if ( CONST_Debug ) var_Dump ( $sSQL );
$aSearchWords = chksql ( $this -> oDB -> getAll ( $sSQL ));
$aNewSearches = array ();
2017-10-06 01:14:48 +03:00
foreach ( $aSearches as $oSearch ) {
2016-09-08 04:16:22 +03:00
foreach ( $aSearchWords as $aSearchTerm ) {
2017-10-06 01:14:48 +03:00
$oNewSearch = clone $oSearch ;
$oNewSearch -> setPoiSearch (
Operator :: TYPE ,
$aSearchTerm [ 'class' ],
2017-10-08 16:26:14 +03:00
$aSearchTerm [ 'type' ]
2017-10-06 01:14:48 +03:00
);
$aNewSearches [] = $oNewSearch ;
2016-09-04 04:19:48 +03:00
}
}
$aSearches = $aNewSearches ;
}
// Split query into phrases
// Commas are used to reduce the search space by indicating where phrases split
2016-09-08 04:16:22 +03:00
if ( $this -> aStructuredQuery ) {
2017-10-12 23:37:44 +03:00
$aInPhrases = $this -> aStructuredQuery ;
2016-09-04 04:19:48 +03:00
$bStructuredPhrases = true ;
2016-09-08 04:16:22 +03:00
} else {
2017-10-12 23:37:44 +03:00
$aInPhrases = explode ( ',' , $sQuery );
2016-09-04 04:19:48 +03:00
$bStructuredPhrases = false ;
}
// Convert each phrase to standard form
// Create a list of standard words
// Get all 'sets' of words
// Generate a complete list of all
$aTokens = array ();
2017-10-12 23:37:44 +03:00
$aPhrases = array ();
foreach ( $aInPhrases as $iPhrase => $sPhrase ) {
$sPhrase = chksql (
$this -> oDB -> getOne ( 'SELECT make_standard_name(' . getDBQuoted ( $sPhrase ) . ')' ),
2017-10-26 22:21:21 +03:00
'Cannot normalize query string (is it a UTF-8 string?)'
2016-09-11 06:22:51 +03:00
);
2017-10-12 23:37:44 +03:00
if ( trim ( $sPhrase )) {
$oPhrase = new Phrase ( $sPhrase , is_string ( $iPhrase ) ? $iPhrase : '' );
$oPhrase -> addTokens ( $aTokens );
$aPhrases [] = $oPhrase ;
2016-09-04 04:19:48 +03:00
}
}
2016-09-08 04:16:22 +03:00
if ( sizeof ( $aTokens )) {
2016-09-04 04:19:48 +03:00
// Check which tokens we have, get the ID numbers
2016-10-29 19:49:21 +03:00
$sSQL = 'SELECT word_id, word_token, word, class, type, country_code, operator, search_name_count' ;
$sSQL .= ' FROM word ' ;
2017-10-26 22:21:21 +03:00
$sSQL .= ' WHERE word_token in (' . join ( ',' , array_map ( 'getDBQuoted' , $aTokens )) . ')' ;
2016-09-04 04:19:48 +03:00
if ( CONST_Debug ) var_Dump ( $sSQL );
$aValidTokens = array ();
2017-10-02 23:31:52 +03:00
$aDatabaseWords = chksql (
$this -> oDB -> getAll ( $sSQL ),
2017-10-26 22:21:21 +03:00
'Could not get word tokens.'
2017-10-02 23:31:52 +03:00
);
2016-09-04 04:19:48 +03:00
$aWordFrequencyScores = array ();
2016-09-08 04:16:22 +03:00
foreach ( $aDatabaseWords as $aToken ) {
2017-10-13 23:23:39 +03:00
// Filter country tokens that do not match restricted countries.
if ( $this -> aCountryCodes
&& $aToken [ 'country_code' ]
&& ! in_array ( $aToken [ 'country_code' ], $this -> aCountryCodes )
2016-09-08 04:16:22 +03:00
) {
2016-09-04 04:19:48 +03:00
continue ;
}
2017-10-14 00:04:12 +03:00
// Special terms need to appear in their normalized form.
if ( $aToken [ 'word' ] && $aToken [ 'class' ]) {
$sNormWord = $this -> normTerm ( $aToken [ 'word' ]);
if ( strpos ( $sNormQuery , $sNormWord ) === false ) {
continue ;
}
}
2016-09-08 04:16:22 +03:00
if ( isset ( $aValidTokens [ $aToken [ 'word_token' ]])) {
2016-09-04 04:19:48 +03:00
$aValidTokens [ $aToken [ 'word_token' ]][] = $aToken ;
2016-09-08 04:16:22 +03:00
} else {
2016-09-04 04:19:48 +03:00
$aValidTokens [ $aToken [ 'word_token' ]] = array ( $aToken );
}
$aWordFrequencyScores [ $aToken [ 'word_id' ]] = $aToken [ 'search_name_count' ] + 1 ;
}
if ( CONST_Debug ) var_Dump ( $aPhrases , $aValidTokens );
2017-07-06 23:48:09 +03:00
// US ZIP+4 codes - if there is no token, merge in the 5-digit ZIP code
2016-09-08 04:16:22 +03:00
foreach ( $aTokens as $sToken ) {
2017-07-06 23:48:09 +03:00
if ( ! isset ( $aValidTokens [ $sToken ]) && preg_match ( '/^([0-9]{5}) [0-9]{4}$/' , $sToken , $aData )) {
2016-09-08 04:16:22 +03:00
if ( isset ( $aValidTokens [ $aData [ 1 ]])) {
foreach ( $aValidTokens [ $aData [ 1 ]] as $aToken ) {
if ( ! $aToken [ 'class' ]) {
if ( isset ( $aValidTokens [ $sToken ])) {
2016-09-04 04:19:48 +03:00
$aValidTokens [ $sToken ][] = $aToken ;
2016-09-08 04:16:22 +03:00
} else {
2016-09-04 04:19:48 +03:00
$aValidTokens [ $sToken ] = array ( $aToken );
}
}
}
}
}
}
2016-09-08 04:16:22 +03:00
foreach ( $aTokens as $sToken ) {
2016-09-04 04:19:48 +03:00
// Unknown single word token with a number - assume it is a house number
2017-10-08 16:26:14 +03:00
if ( ! isset ( $aValidTokens [ ' ' . $sToken ]) && strpos ( $sToken , ' ' ) === false && preg_match ( '/^[0-9]+$/' , $sToken )) {
$aValidTokens [ ' ' . $sToken ] = array ( array ( 'class' => 'place' , 'type' => 'house' , 'word_token' => ' ' . $sToken ));
2016-09-04 04:19:48 +03:00
}
}
// Any words that have failed completely?
// TODO: suggestions
2017-10-14 00:04:12 +03:00
$aGroupedSearches = $this -> getGroupedSearches ( $aSearches , $aPhrases , $aValidTokens , $bStructuredPhrases );
2016-09-04 04:19:48 +03:00
2016-09-08 04:16:22 +03:00
if ( $this -> bReverseInPlan ) {
2016-09-04 04:19:48 +03:00
// 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 );
2017-10-12 23:37:44 +03:00
$aPhrases [ 0 ] -> invertWordSets ();
2016-09-08 04:16:22 +03:00
if ( sizeof ( $aPhrases ) > 1 ) {
2017-10-12 23:37:44 +03:00
$aPhrases [ sizeof ( $aPhrases ) - 1 ] -> invertWordSets ();
2016-09-04 04:19:48 +03:00
}
2017-10-14 00:04:12 +03:00
$aReverseGroupedSearches = $this -> getGroupedSearches ( $aSearches , $aPhrases , $aValidTokens , false );
2016-09-04 04:19:48 +03:00
2016-09-08 04:16:22 +03:00
foreach ( $aGroupedSearches as $aSearches ) {
foreach ( $aSearches as $aSearch ) {
2017-10-06 01:14:48 +03:00
if ( ! isset ( $aReverseGroupedSearches [ $aSearch -> getRank ()])) {
$aReverseGroupedSearches [ $aSearch -> getRank ()] = array ();
2016-09-04 04:19:48 +03:00
}
2017-10-06 01:14:48 +03:00
$aReverseGroupedSearches [ $aSearch -> getRank ()][] = $aSearch ;
2016-09-04 04:19:48 +03:00
}
}
$aGroupedSearches = $aReverseGroupedSearches ;
ksort ( $aGroupedSearches );
}
2016-09-08 04:16:22 +03:00
} else {
2016-09-04 04:19:48 +03:00
// Re-group the searches by their score, junk anything over 20 as just not worth trying
$aGroupedSearches = array ();
2016-09-08 04:16:22 +03:00
foreach ( $aSearches as $aSearch ) {
2017-10-06 01:14:48 +03:00
if ( $aSearch -> getRank () < $this -> iMaxRank ) {
if ( ! isset ( $aGroupedSearches [ $aSearch -> getRank ()])) $aGroupedSearches [ $aSearch -> getRank ()] = array ();
$aGroupedSearches [ $aSearch -> getRank ()][] = $aSearch ;
2016-09-04 04:19:48 +03:00
}
}
ksort ( $aGroupedSearches );
}
// Filter out duplicate searches
$aSearchHash = array ();
2016-09-08 04:16:22 +03:00
foreach ( $aGroupedSearches as $iGroup => $aSearches ) {
foreach ( $aSearches as $iSearch => $aSearch ) {
2016-09-04 04:19:48 +03:00
$sHash = serialize ( $aSearch );
2016-09-08 04:16:22 +03:00
if ( isset ( $aSearchHash [ $sHash ])) {
2016-09-04 04:19:48 +03:00
unset ( $aGroupedSearches [ $iGroup ][ $iSearch ]);
if ( sizeof ( $aGroupedSearches [ $iGroup ]) == 0 ) unset ( $aGroupedSearches [ $iGroup ]);
2016-09-08 04:16:22 +03:00
} else {
2016-09-04 04:19:48 +03:00
$aSearchHash [ $sHash ] = 1 ;
}
}
}
if ( CONST_Debug ) _debugDumpGroupedSearches ( $aGroupedSearches , $aValidTokens );
2017-10-09 23:55:50 +03:00
// Start the search process
2016-09-04 04:19:48 +03:00
$iGroupLoop = 0 ;
$iQueryLoop = 0 ;
2016-09-08 04:16:22 +03:00
foreach ( $aGroupedSearches as $iGroupedRank => $aSearches ) {
2016-09-04 04:19:48 +03:00
$iGroupLoop ++ ;
2017-10-07 13:01:56 +03:00
foreach ( $aSearches as $oSearch ) {
2016-09-04 04:19:48 +03:00
$iQueryLoop ++ ;
2016-09-08 04:16:22 +03:00
if ( CONST_Debug ) {
2017-10-09 23:55:50 +03:00
echo " <hr><b>Search Loop, group $iGroupLoop , loop $iQueryLoop </b> " ;
_debugDumpGroupedSearches ( array ( $iGroupedRank => array ( $oSearch )), $aValidTokens );
2016-09-08 04:16:22 +03:00
}
2016-09-04 04:19:48 +03:00
2017-10-19 00:54:47 +03:00
$aResults += $oSearch -> query (
2017-10-09 23:55:50 +03:00
$this -> oDB ,
$aWordFrequencyScores ,
$this -> iMinAddressRank ,
$this -> iMaxAddressRank ,
$this -> iLimit
);
2017-07-06 00:35:22 +03:00
2016-09-04 04:19:48 +03:00
if ( $iQueryLoop > 20 ) break ;
}
2017-10-19 00:54:47 +03:00
if ( sizeof ( $aResults ) && ( $this -> iMinAddressRank != 0 || $this -> iMaxAddressRank != 30 )) {
2016-09-04 04:19:48 +03:00
// 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
2017-10-19 00:54:47 +03:00
$aFilterSql = array ();
$sPlaceIds = Result :: joinIdsByTable ( $aResults , Result :: TABLE_PLACEX );
if ( $sPlaceIds ) {
$sSQL = 'SELECT place_id FROM placex ' ;
$sSQL .= 'WHERE place_id in (' . $sPlaceIds . ') ' ;
2017-10-26 22:21:21 +03:00
$sSQL .= ' AND (' ;
2017-10-19 00:54:47 +03:00
$sSQL .= " placex.rank_address between $this->iMinAddressRank and $this->iMaxAddressRank " ;
if ( 14 >= $this -> iMinAddressRank && 14 <= $this -> iMaxAddressRank ) {
$sSQL .= " OR (extratags->'place') = 'city' " ;
}
if ( $this -> aAddressRankList ) {
2017-10-26 22:21:21 +03:00
$sSQL .= ' OR placex.rank_address in (' . join ( ',' , $this -> aAddressRankList ) . ')' ;
2017-10-19 00:54:47 +03:00
}
2017-10-26 22:21:21 +03:00
$sSQL .= ')' ;
2017-10-19 00:54:47 +03:00
$aFilterSql [] = $sSQL ;
2017-10-03 13:10:27 +03:00
}
2017-10-19 00:54:47 +03:00
$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 ) {
2017-10-26 22:21:21 +03:00
$sSQL .= ' OR lp.rank_address in (' . join ( ',' , $this -> aAddressRankList ) . ')' ;
2017-10-19 00:54:47 +03:00
}
2017-10-26 22:21:21 +03:00
$sSQL .= ') ' ;
2017-10-19 00:54:47 +03:00
$aFilterSql [] = $sSQL ;
2016-09-04 04:19:48 +03:00
}
2017-10-19 00:54:47 +03:00
$aFilteredIDs = array ();
if ( $aFilterSql ) {
$sSQL = join ( ' UNION ' , $aFilterSql );
if ( CONST_Debug ) var_dump ( $sSQL );
$aFilteredIDs = chksql ( $this -> oDB -> getCol ( $sSQL ));
2017-10-02 21:42:37 +03:00
}
2017-10-19 00:54:47 +03:00
2016-09-04 04:19:48 +03:00
$tempIDs = array ();
2017-10-19 00:54:47 +03:00
foreach ( $aResults as $oResult ) {
if (( $this -> iMaxAddressRank == 30 &&
( $oResult -> iTable == Result :: TABLE_OSMLINE
|| $oResult -> iTable == Result :: TABLE_AUX
|| $oResult -> iTable == Result :: TABLE_TIGER ))
|| in_array ( $oResult -> iId , $aFilteredIDs )
) {
$tempIDs [ $oResult -> iId ] = $oResult ;
}
2016-09-04 04:19:48 +03:00
}
2017-10-19 00:54:47 +03:00
$aResults = $tempIDs ;
2016-09-04 04:19:48 +03:00
}
2017-10-19 00:54:47 +03:00
if ( sizeof ( $aResults )) break ;
2016-09-04 04:19:48 +03:00
if ( $iGroupLoop > 4 ) break ;
if ( $iQueryLoop > 30 ) break ;
}
2016-09-08 04:16:22 +03:00
} else {
2016-09-04 04:19:48 +03:00
// Just interpret as a reverse geocode
2016-09-30 23:42:12 +03:00
$oReverse = new ReverseGeocode ( $this -> oDB );
2016-09-04 04:19:48 +03:00
$oReverse -> setZoom ( 18 );
2017-10-20 16:41:26 +03:00
$oLookup = $oReverse -> lookupPoint ( $oCtx -> sqlNear , false );
2016-09-04 04:19:48 +03:00
2017-10-26 22:21:21 +03:00
if ( CONST_Debug ) var_dump ( 'Reverse search' , $aLookup );
2016-09-04 04:19:48 +03:00
2017-10-20 16:41:26 +03:00
if ( $oLookup ) {
$aResults = array ( $oLookup -> iId => $oLookup );
2016-09-08 04:16:22 +03:00
}
2016-09-04 04:19:48 +03:00
}
// No results? Done
2017-10-22 22:59:08 +03:00
if ( ! sizeof ( $aResults )) {
2016-09-08 04:16:22 +03:00
if ( $this -> bFallback ) {
if ( $this -> fallbackStructuredQuery ()) {
2016-09-04 04:19:48 +03:00
return $this -> lookup ();
}
}
return array ();
}
2017-10-22 22:59:08 +03:00
if ( $this -> aAddressRankList ) {
2017-10-24 00:28:00 +03:00
$this -> oPlaceLookup -> setAddressRankList ( $this -> aAddressRankList );
2017-10-22 22:59:08 +03:00
}
2017-10-24 00:28:00 +03:00
$this -> oPlaceLookup -> setAllowedTypesSQLList ( $this -> sAllowedTypesSQLList );
$this -> oPlaceLookup -> setLanguagePreference ( $this -> aLangPrefOrder );
2017-10-22 22:59:08 +03:00
if ( $oCtx -> hasNearPoint ()) {
2017-10-24 00:28:00 +03:00
$this -> oPlaceLookup -> setAnchorSql ( $oCtx -> sqlNear );
2017-10-22 22:59:08 +03:00
}
2017-10-24 00:28:00 +03:00
$aSearchResults = $this -> oPlaceLookup -> lookup ( $aResults );
2017-10-22 22:59:08 +03:00
2016-09-04 04:19:48 +03:00
$aClassType = getClassTypesWithImportance ();
2016-09-11 06:22:51 +03:00
$aRecheckWords = preg_split ( '/\b[\s,\\-]*/u' , $sQuery );
2016-09-08 04:16:22 +03:00
foreach ( $aRecheckWords as $i => $sWord ) {
2017-02-28 23:40:05 +03:00
if ( ! preg_match ( '/[\pL\pN]/' , $sWord )) unset ( $aRecheckWords [ $i ]);
2016-09-04 04:19:48 +03:00
}
2016-09-08 04:16:22 +03:00
if ( CONST_Debug ) {
2016-09-14 04:16:46 +03:00
echo '<i>Recheck words:<\i>' ;
var_dump ( $aRecheckWords );
2016-09-08 04:16:22 +03:00
}
2016-09-04 04:19:48 +03:00
2017-10-22 22:59:08 +03:00
foreach ( $aSearchResults as $iIdx => $aResult ) {
2016-09-04 04:19:48 +03:00
// Default
$fDiameter = getResultDiameter ( $aResult );
2017-10-24 00:28:00 +03:00
$aOutlineResult = $this -> oPlaceLookup -> getOutlines ( $aResult [ 'place_id' ], $aResult [ 'lon' ], $aResult [ 'lat' ], $fDiameter / 2 );
2016-09-08 04:16:22 +03:00
if ( $aOutlineResult ) {
2016-09-04 04:19:48 +03:00
$aResult = array_merge ( $aResult , $aOutlineResult );
}
2017-10-22 22:59:08 +03:00
2016-09-08 04:16:22 +03:00
if ( $aResult [ 'extra_place' ] == 'city' ) {
2016-09-04 04:19:48 +03:00
$aResult [ 'class' ] = 'place' ;
$aResult [ 'type' ] = 'city' ;
$aResult [ 'rank_search' ] = 16 ;
}
// Is there an icon set for this type of result?
if ( isset ( $aClassType [ $aResult [ 'class' ] . ':' . $aResult [ 'type' ]][ 'icon' ])
2016-09-11 06:22:51 +03:00
&& $aClassType [ $aResult [ 'class' ] . ':' . $aResult [ 'type' ]][ 'icon' ]
2016-09-08 04:16:22 +03:00
) {
2016-09-04 04:19:48 +03:00
$aResult [ 'icon' ] = CONST_Website_BaseURL . 'images/mapicons/' . $aClassType [ $aResult [ 'class' ] . ':' . $aResult [ 'type' ]][ 'icon' ] . '.p.20.png' ;
}
if ( isset ( $aClassType [ $aResult [ 'class' ] . ':' . $aResult [ 'type' ] . ':' . $aResult [ 'admin_level' ]][ 'label' ])
2016-09-11 06:22:51 +03:00
&& $aClassType [ $aResult [ 'class' ] . ':' . $aResult [ 'type' ] . ':' . $aResult [ 'admin_level' ]][ 'label' ]
2016-09-08 04:16:22 +03:00
) {
2016-09-04 04:19:48 +03:00
$aResult [ 'label' ] = $aClassType [ $aResult [ 'class' ] . ':' . $aResult [ 'type' ] . ':' . $aResult [ 'admin_level' ]][ 'label' ];
2016-09-08 04:16:22 +03:00
} elseif ( isset ( $aClassType [ $aResult [ 'class' ] . ':' . $aResult [ 'type' ]][ 'label' ])
2016-09-11 06:22:51 +03:00
&& $aClassType [ $aResult [ 'class' ] . ':' . $aResult [ 'type' ]][ 'label' ]
2016-09-08 04:16:22 +03:00
) {
2016-09-04 04:19:48 +03:00
$aResult [ 'label' ] = $aClassType [ $aResult [ 'class' ] . ':' . $aResult [ 'type' ]][ 'label' ];
}
// if tag '&addressdetails=1' is set in query
2016-09-08 04:16:22 +03:00
if ( $this -> bIncludeAddressDetails ) {
2016-09-04 04:19:48 +03:00
// getAddressDetails() is defined in lib.php and uses the SQL function get_addressdata in functions.sql
2017-10-19 00:54:47 +03:00
$aResult [ 'address' ] = getAddressDetails ( $this -> oDB , $sLanguagePrefArraySQL , $aResult [ 'place_id' ], $aResult [ 'country_code' ], $aResults [ $aResult [ 'place_id' ]] -> iHouseNumber );
2016-09-08 04:16:22 +03:00
if ( $aResult [ 'extra_place' ] == 'city' && ! isset ( $aResult [ 'address' ][ 'city' ])) {
2016-10-09 22:14:59 +03:00
$aResult [ 'address' ] = array_merge ( array ( 'city' => array_values ( $aResult [ 'address' ])[ 0 ]), $aResult [ 'address' ]);
2016-09-04 04:19:48 +03:00
}
}
2016-09-08 04:16:22 +03:00
2017-10-10 23:42:23 +03:00
$aResult [ 'name' ] = $aResult [ 'langaddress' ];
2017-10-14 00:11:09 +03:00
if ( $oCtx -> hasNearPoint ()) {
2017-10-10 23:42:23 +03:00
$aResult [ 'importance' ] = 0.001 ;
$aResult [ 'foundorder' ] = $aResult [ 'addressimportance' ];
} else {
// Adjust importance for the number of exact string matches in the result
2017-10-22 22:59:08 +03:00
$aResult [ 'importance' ] *= $this -> viewboxImportanceFactor (
$aResult [ 'lon' ],
$aResult [ 'lat' ]
);
2017-10-10 23:42:23 +03:00
$aResult [ 'importance' ] = max ( 0.001 , $aResult [ 'importance' ]);
$iCountWords = 0 ;
$sAddress = $aResult [ 'langaddress' ];
foreach ( $aRecheckWords as $i => $sWord ) {
if ( stripos ( $sAddress , $sWord ) !== false ) {
$iCountWords ++ ;
2017-10-26 22:21:21 +03:00
if ( preg_match ( '/(^|,)\s*' . preg_quote ( $sWord , '/' ) . '\s*(,|$)/' , $sAddress )) $iCountWords += 0.1 ;
2017-10-10 23:42:23 +03:00
}
2016-09-04 04:19:48 +03:00
}
2017-10-10 23:42:23 +03:00
$aResult [ 'importance' ] = $aResult [ 'importance' ] + ( $iCountWords * 0.1 ); // 0.1 is a completely arbitrary number but something in the range 0.1 to 0.5 would seem right
2016-09-04 04:19:48 +03:00
2017-10-10 23:42:23 +03:00
// secondary ordering (for results with same importance (the smaller the better):
// - approximate importance of address parts
$aResult [ 'foundorder' ] = - $aResult [ 'addressimportance' ] / 10 ;
// - number of exact matches from the query
2017-10-19 00:54:47 +03:00
$aResult [ 'foundorder' ] -= $aResults [ $aResult [ 'place_id' ]] -> iExactMatches ;
2017-10-10 23:42:23 +03:00
// - importance of the class/type
if ( isset ( $aClassType [ $aResult [ 'class' ] . ':' . $aResult [ 'type' ]][ 'importance' ])
&& $aClassType [ $aResult [ 'class' ] . ':' . $aResult [ 'type' ]][ 'importance' ]
) {
$aResult [ 'foundorder' ] += 0.0001 * $aClassType [ $aResult [ 'class' ] . ':' . $aResult [ 'type' ]][ 'importance' ];
} else {
$aResult [ 'foundorder' ] += 0.01 ;
}
2016-09-04 04:19:48 +03:00
}
2016-09-08 04:16:22 +03:00
if ( CONST_Debug ) var_dump ( $aResult );
2017-10-22 22:59:08 +03:00
$aSearchResults [ $iIdx ] = $aResult ;
2016-09-04 04:19:48 +03:00
}
uasort ( $aSearchResults , 'byImportance' );
$aOSMIDDone = array ();
$aClassTypeNameDone = array ();
$aToFilter = $aSearchResults ;
$aSearchResults = array ();
2017-10-22 22:59:08 +03:00
if ( CONST_Debug ) var_dump ( $aToFilter );
2016-09-04 04:19:48 +03:00
$bFirst = true ;
2017-10-22 22:59:08 +03:00
foreach ( $aToFilter as $aResult ) {
2016-09-04 04:19:48 +03:00
$this -> aExcludePlaceIDs [ $aResult [ 'place_id' ]] = $aResult [ 'place_id' ];
2016-09-08 04:16:22 +03:00
if ( $bFirst ) {
2016-09-04 04:19:48 +03:00
$fLat = $aResult [ 'lat' ];
$fLon = $aResult [ 'lon' ];
if ( isset ( $aResult [ 'zoom' ])) $iZoom = $aResult [ 'zoom' ];
$bFirst = false ;
}
2017-10-24 00:28:00 +03:00
if ( ! $this -> oPlaceLookup -> doDeDupe () || ( ! isset ( $aOSMIDDone [ $aResult [ 'osm_type' ] . $aResult [ 'osm_id' ]])
2016-09-11 06:22:51 +03:00
&& ! isset ( $aClassTypeNameDone [ $aResult [ 'osm_type' ] . $aResult [ 'class' ] . $aResult [ 'type' ] . $aResult [ 'name' ] . $aResult [ 'admin_level' ]]))
2016-09-08 04:16:22 +03:00
) {
2016-09-04 04:19:48 +03:00
$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 ( sizeof ( $aSearchResults ) >= $this -> iFinalLimit ) break ;
}
2017-10-22 22:59:08 +03:00
if ( CONST_Debug ) var_dump ( $aSearchResults );
2016-09-04 04:19:48 +03:00
return $aSearchResults ;
} // end lookup()
} // end class