2         @define('CONST_ConnectionBucket_PageType', 'Search');
 
   4         require_once(dirname(dirname(__FILE__)).'/lib/init-website.php');
 
   5         require_once(CONST_BasePath.'/lib/log.php');
 
   7         ini_set('memory_limit', '200M');
 
  12         $fLat = CONST_Default_Lat;
 
  13         $fLon = CONST_Default_Lon;
 
  14         $iZoom = CONST_Default_Zoom;
 
  15         $bBoundingBoxSearch = isset($_GET['bounded'])?(bool)$_GET['bounded']:false;
 
  16         $sOutputFormat = 'html';
 
  17         $aSearchResults = array();
 
  18         $aExcludePlaceIDs = array();
 
  19         $sCountryCodesSQL = false;
 
  20         $bDeDupe = isset($_GET['dedupe'])?(bool)$_GET['dedupe']:true;
 
  21         $bReverseInPlan = false;
 
  22         $iFinalLimit = isset($_GET['limit'])?(int)$_GET['limit']:10;
 
  23         $iOffset = isset($_GET['offset'])?(int)$_GET['offset']:0;
 
  25         if ($iFinalLimit > 50) $iFinalLimit = 50;
 
  26         $iLimit = $iFinalLimit + min($iFinalLimit, 10);
 
  28         $iMaxAddressRank = 30;
 
  29         $aAddressRankList = array();
 
  30         $sAllowedTypesSQLList = false;
 
  33         if (isset($_GET['format']) && ($_GET['format'] == 'html' || $_GET['format'] == 'xml' || $_GET['format'] == 'json' ||  $_GET['format'] == 'jsonv2'))
 
  35                 $sOutputFormat = $_GET['format'];
 
  38         // Show / use polygons
 
  39         $bShowPolygons = (boolean)isset($_GET['polygon']) && $_GET['polygon'];
 
  40         if ($sOutputFormat == 'html')
 
  42                 $bAsText = $bShowPolygons;
 
  43                 $bShowPolygons = false;
 
  50                 $bAsGeoJSON = (boolean)isset($_GET['polygon_geojson']) && $_GET['polygon_geojson'];
 
  51                 $bAsKML = (boolean)isset($_GET['polygon_kml']) && $_GET['polygon_kml'];
 
  52                 $bAsSVG = (boolean)isset($_GET['polygon_svg']) && $_GET['polygon_svg'];
 
  53                 $bAsText = (boolean)isset($_GET['polygon_text']) && $_GET['polygon_text'];
 
  54                 if ((($bShowPolygons?1:0)
 
  59                         ) > CONST_PolygonOutput_MaximumTypes)
 
  61                         if (CONST_PolygonOutput_MaximumTypes)
 
  63                                 userError("Select only ".CONST_PolygonOutput_MaximumTypes." polgyon output option");
 
  67                                 userError("Polygon output is disabled");
 
  73         // Show address breakdown
 
  74         $bShowAddressDetails = isset($_GET['addressdetails']) && $_GET['addressdetails'];
 
  77         $aLangPrefOrder = getPreferredLanguages();
 
  78         if (isset($aLangPrefOrder['name:de'])) $bReverseInPlan = true;
 
  79         if (isset($aLangPrefOrder['name:ru'])) $bReverseInPlan = true;
 
  80         if (isset($aLangPrefOrder['name:ja'])) $bReverseInPlan = true;
 
  82         $sLanguagePrefArraySQL = "ARRAY[".join(',',array_map("getDBQuoted",$aLangPrefOrder))."]";
 
  84         if (isset($_GET['exclude_place_ids']) && $_GET['exclude_place_ids'])
 
  86                 foreach(explode(',',$_GET['exclude_place_ids']) as $iExcludedPlaceID)
 
  88                         $iExcludedPlaceID = (int)$iExcludedPlaceID;
 
  89                         if ($iExcludedPlaceID) $aExcludePlaceIDs[$iExcludedPlaceID] = $iExcludedPlaceID;
 
  93         // Only certain ranks of feature
 
  94         if (isset($_GET['featureType']) && !isset($_GET['featuretype'])) $_GET['featuretype'] = $_GET['featureType'];
 
  96         if (isset($_GET['featuretype']))
 
  98                 switch($_GET['featuretype'])
 
 101                         $iMinAddressRank = $iMaxAddressRank = 4;
 
 104                         $iMinAddressRank = $iMaxAddressRank = 8;
 
 107                         $iMinAddressRank = 14;
 
 108                         $iMaxAddressRank = 16;
 
 111                         $iMinAddressRank = 8;
 
 112                         $iMaxAddressRank = 20;
 
 117         if (isset($_GET['countrycodes']))
 
 119                 $aCountryCodes = array();
 
 120                 foreach(explode(',',$_GET['countrycodes']) as $sCountryCode)
 
 122                         if (preg_match('/^[a-zA-Z][a-zA-Z]$/', $sCountryCode))
 
 124                                 $aCountryCodes[] = "'".strtolower($sCountryCode)."'";
 
 127                 $sCountryCodesSQL = join(',', $aCountryCodes);
 
 131         $sQuery = (isset($_GET['q'])?trim($_GET['q']):'');
 
 132         if (!$sQuery && isset($_SERVER['PATH_INFO']) && $_SERVER['PATH_INFO'][0] == '/')
 
 134                 $sQuery = substr($_SERVER['PATH_INFO'], 1);
 
 136                 // reverse order of '/' separated string
 
 137                 $aPhrases = explode('/', $sQuery);
 
 138                 $aPhrases = array_reverse($aPhrases);
 
 139                 $sQuery = join(', ',$aPhrases);
 
 143         $aStructuredOptions = array(
 
 144                                 array('amenity', 26, 30, false),
 
 145                                 array('street', 26, 30, false),
 
 146                                 array('city', 14, 24, false),
 
 147                                 array('county', 9, 13, false),
 
 148                                 array('state', 8, 8, false),
 
 149                                 array('country', 4, 4, false),
 
 150                                 array('postalcode', 5, 11, array(5, 11)),
 
 152         $aStructuredQuery = array();
 
 153         $sAllowedTypesSQLList = '';
 
 154         foreach($aStructuredOptions as $aStructuredOption)
 
 156                 loadStructuredAddressElement($aStructuredQuery, $iMinAddressRank, $iMaxAddressRank, $aAddressRankList, $_GET, $aStructuredOption[0], $aStructuredOption[1], $aStructuredOption[2], $aStructuredOption[3]);
 
 158         if (sizeof($aStructuredQuery) > 0) 
 
 160                 $sQuery = join(', ', $aStructuredQuery);
 
 161                 if ($iMaxAddressRank < 30)
 
 163                         $sAllowedTypesSQLList = '(\'place\',\'boundary\')';
 
 169                 $hLog = logStart($oDB, 'search', $sQuery, $aLangPrefOrder);
 
 171                 // Hack to make it handle "new york, ny" (and variants) correctly
 
 172                 $sQuery = str_ireplace(array('New York, ny','new york, new york', 'New York ny','new york new york'), 'new york city, ny', $sQuery);
 
 173                 if (isset($aLangPrefOrder['name:en']))
 
 175                         $sQuery = preg_replace('/,\s*il\s*(,|$)/',', illinois\1', $sQuery);
 
 176                         $sQuery = preg_replace('/,\s*al\s*(,|$)/',', alabama\1', $sQuery);
 
 177                         $sQuery = preg_replace('/,\s*la\s*(,|$)/',', louisiana\1', $sQuery);
 
 180                 // If we have a view box create the SQL
 
 181                 // Small is the actual view box, Large is double (on each axis) that
 
 182                 $sViewboxCentreSQL = $sViewboxSmallSQL = $sViewboxLargeSQL = false;
 
 183                 if (isset($_GET['viewboxlbrt']) && $_GET['viewboxlbrt'])
 
 185                         $aCoOrdinatesLBRT = explode(',',$_GET['viewboxlbrt']);
 
 186                         $_GET['viewbox'] = $aCoOrdinatesLBRT[0].','.$aCoOrdinatesLBRT[3].','.$aCoOrdinatesLBRT[2].','.$aCoOrdinatesLBRT[1];
 
 188                 if (isset($_GET['viewbox']) && $_GET['viewbox'])
 
 190                         $aCoOrdinates = explode(',',$_GET['viewbox']);
 
 191                         $sViewboxSmallSQL = "ST_SetSRID(ST_MakeBox2D(ST_Point(".(float)$aCoOrdinates[0].",".(float)$aCoOrdinates[1]."),ST_Point(".(float)$aCoOrdinates[2].",".(float)$aCoOrdinates[3].")),4326)";
 
 192                         $fHeight = $aCoOrdinates[0]-$aCoOrdinates[2];
 
 193                         $fWidth = $aCoOrdinates[1]-$aCoOrdinates[3];
 
 194                         $aCoOrdinates[0] += $fHeight;
 
 195                         $aCoOrdinates[2] -= $fHeight;
 
 196                         $aCoOrdinates[1] += $fWidth;
 
 197                         $aCoOrdinates[3] -= $fWidth;
 
 198                         $sViewboxLargeSQL = "ST_SetSRID(ST_MakeBox2D(ST_Point(".(float)$aCoOrdinates[0].",".(float)$aCoOrdinates[1]."),ST_Point(".(float)$aCoOrdinates[2].",".(float)$aCoOrdinates[3].")),4326)";
 
 202                         $bBoundingBoxSearch = false;
 
 204                 if (isset($_GET['route']) && $_GET['route'] && isset($_GET['routewidth']) && $_GET['routewidth'])
 
 206                         $aPoints = explode(',',$_GET['route']);
 
 207                         if (sizeof($aPoints) % 2 != 0)
 
 209                                 userError("Uneven number of points");
 
 212                         $sViewboxCentreSQL = "ST_SetSRID('LINESTRING(";
 
 214                         foreach($aPoints as $i => $fPoint)
 
 218                                         if ($i != 1) $sViewboxCentreSQL .= ",";
 
 219                                         $sViewboxCentreSQL .= ((float)$fPoint).' '.$fPrevCoord;
 
 223                                         $fPrevCoord = (float)$fPoint;
 
 226                         $sViewboxCentreSQL .= ")'::geometry,4326)";
 
 228                         $sSQL = "select st_buffer(".$sViewboxCentreSQL.",".(float)($_GET['routewidth']/69).")";
 
 229                         $sViewboxSmallSQL = $oDB->getOne($sSQL);
 
 230                         if (PEAR::isError($sViewboxSmallSQL))
 
 232                                 failInternalError("Could not get small viewbox.", $sSQL, $sViewboxSmallSQL);
 
 234                         $sViewboxSmallSQL = "'".$sViewboxSmallSQL."'::geometry";
 
 236                         $sSQL = "select st_buffer(".$sViewboxCentreSQL.",".(float)($_GET['routewidth']/30).")";
 
 237                         $sViewboxLargeSQL = $oDB->getOne($sSQL);
 
 238                         if (PEAR::isError($sViewboxLargeSQL))
 
 240                                 failInternalError("Could not get large viewbox.", $sSQL, $sViewboxLargeSQL);
 
 242                         $sViewboxLargeSQL = "'".$sViewboxLargeSQL."'::geometry";
 
 243                         $bBoundingBoxSearch = true;
 
 246                 // Do we have anything that looks like a lat/lon pair?
 
 247                 if (preg_match('/\\b([NS])[ ]+([0-9]+[0-9.]*)[ ]+([0-9.]+)?[, ]+([EW])[ ]+([0-9]+)[ ]+([0-9]+[0-9.]*)?\\b/', $sQuery, $aData))
 
 249                         $fQueryLat = ($aData[1]=='N'?1:-1) * ($aData[2] + $aData[3]/60);
 
 250                         $fQueryLon = ($aData[4]=='E'?1:-1) * ($aData[5] + $aData[6]/60);
 
 251                         if ($fQueryLat <= 90.1 && $fQueryLat >= -90.1 && $fQueryLon <= 180.1 && $fQueryLon >= -180.1)
 
 253                                 $_GET['nearlat'] = $fQueryLat;
 
 254                                 $_GET['nearlon'] = $fQueryLon;
 
 255                                 $sQuery = trim(str_replace($aData[0], ' ', $sQuery));
 
 258                 elseif (preg_match('/\\b([0-9]+)[ ]+([0-9]+[0-9.]*)?[ ]+([NS])[, ]+([0-9]+)[ ]+([0-9]+[0-9.]*)?[ ]+([EW])\\b/', $sQuery, $aData))
 
 260                         $fQueryLat = ($aData[3]=='N'?1:-1) * ($aData[1] + $aData[2]/60);
 
 261                         $fQueryLon = ($aData[6]=='E'?1:-1) * ($aData[4] + $aData[5]/60);
 
 262                         if ($fQueryLat <= 90.1 && $fQueryLat >= -90.1 && $fQueryLon <= 180.1 && $fQueryLon >= -180.1)
 
 264                                 $_GET['nearlat'] = $fQueryLat;
 
 265                                 $_GET['nearlon'] = $fQueryLon;
 
 266                                 $sQuery = trim(str_replace($aData[0], ' ', $sQuery));
 
 269                 elseif (preg_match('/(\\[|^|\\b)(-?[0-9]+[0-9]*\\.[0-9]+)[, ]+(-?[0-9]+[0-9]*\\.[0-9]+)(\\]|$|\\b)/', $sQuery, $aData))
 
 271                         $fQueryLat = $aData[2];
 
 272                         $fQueryLon = $aData[3];
 
 273                         if ($fQueryLat <= 90.1 && $fQueryLat >= -90.1 && $fQueryLon <= 180.1 && $fQueryLon >= -180.1)
 
 275                                 $_GET['nearlat'] = $fQueryLat;
 
 276                                 $_GET['nearlon'] = $fQueryLon;
 
 277                                 $sQuery = trim(str_replace($aData[0], ' ', $sQuery));
 
 281                 if ($sQuery || $aStructuredQuery)
 
 283                         // Start with a blank search
 
 285                                 array('iSearchRank' => 0, 'iNamePhrase' => -1, 'sCountryCode' => false, 'aName'=>array(), 'aAddress'=>array(), 'aFullNameAddress'=>array(),
 
 286                                       'aNameNonSearch'=>array(), 'aAddressNonSearch'=>array(),
 
 287                                       'sOperator'=>'', 'aFeatureName' => array(), 'sClass'=>'', 'sType'=>'', 'sHouseNumber'=>'', 'fLat'=>'', 'fLon'=>'', 'fRadius'=>'')
 
 290                         $sNearPointSQL = false;
 
 291                         if (isset($_GET['nearlat']) && isset($_GET['nearlon']))
 
 293                                 $sNearPointSQL = "ST_SetSRID(ST_Point(".(float)$_GET['nearlon'].",".(float)$_GET['nearlat']."),4326)";
 
 294                                 $aSearches[0]['fLat'] = (float)$_GET['nearlat'];
 
 295                                 $aSearches[0]['fLon'] = (float)$_GET['nearlon'];
 
 296                                 $aSearches[0]['fRadius'] = 0.1;
 
 299                         $bSpecialTerms = false;
 
 300                         preg_match_all('/\\[(.*)=(.*)\\]/', $sQuery, $aSpecialTermsRaw, PREG_SET_ORDER);
 
 301                         $aSpecialTerms = array();
 
 302                         foreach($aSpecialTermsRaw as $aSpecialTerm)
 
 304                                 $sQuery = str_replace($aSpecialTerm[0], ' ', $sQuery);
 
 305                                 $aSpecialTerms[strtolower($aSpecialTerm[1])] = $aSpecialTerm[2];
 
 308                         preg_match_all('/\\[([\\w ]*)\\]/u', $sQuery, $aSpecialTermsRaw, PREG_SET_ORDER);
 
 309                         $aSpecialTerms = array();
 
 310                         if (isset($aStructuredQuery['amenity']) && $aStructuredQuery['amenity'])
 
 312                                 $aSpecialTermsRaw[] = array('['.$aStructuredQuery['amenity'].']', $aStructuredQuery['amenity']);
 
 313                                 unset($aStructuredQuery['amenity']);
 
 315                         foreach($aSpecialTermsRaw as $aSpecialTerm)
 
 317                                 $sQuery = str_replace($aSpecialTerm[0], ' ', $sQuery);
 
 318                                 $sToken = $oDB->getOne("select make_standard_name('".$aSpecialTerm[1]."') as string");
 
 319                                 $sSQL = 'select * from (select word_id,word_token, word, class, type, country_code, operator';
 
 320                                 $sSQL .= ' from word where word_token in (\' '.$sToken.'\')) as x where (class is not null and class not in (\'place\')) or country_code is not null';
 
 321                                 if (CONST_Debug) var_Dump($sSQL);
 
 322                                 $aSearchWords = $oDB->getAll($sSQL);
 
 323                                 $aNewSearches = array();
 
 324                                 foreach($aSearches as $aSearch)
 
 326                                         foreach($aSearchWords as $aSearchTerm)
 
 328                                                 $aNewSearch = $aSearch;
 
 329                                                 if ($aSearchTerm['country_code'])
 
 331                                                         $aNewSearch['sCountryCode'] = strtolower($aSearchTerm['country_code']);
 
 332                                                         $aNewSearches[] = $aNewSearch;
 
 333                                                         $bSpecialTerms = true;
 
 335                                                 if ($aSearchTerm['class'])
 
 337                                                         $aNewSearch['sClass'] = $aSearchTerm['class'];
 
 338                                                         $aNewSearch['sType'] = $aSearchTerm['type'];
 
 339                                                         $aNewSearches[] = $aNewSearch;
 
 340                                                         $bSpecialTerms = true;
 
 344                                 $aSearches = $aNewSearches;
 
 347                         // Split query into phrases
 
 348                         // Commas are used to reduce the search space by indicating where phrases split
 
 349                         if (sizeof($aStructuredQuery) > 0)
 
 351                                 $aPhrases = $aStructuredQuery;
 
 352                                 $bStructuredPhrases = true;
 
 356                                 $aPhrases = explode(',',$sQuery);
 
 357                                 $bStructuredPhrases = false;
 
 360                         // Convert each phrase to standard form
 
 361                         // Create a list of standard words
 
 362                         // Get all 'sets' of words
 
 363                         // Generate a complete list of all
 
 365                         foreach($aPhrases as $iPhrase => $sPhrase)
 
 367                                 $aPhrase = $oDB->getRow("select make_standard_name('".pg_escape_string($sPhrase)."') as string");
 
 368                                 if (PEAR::isError($aPhrase))
 
 370                                         userError("Illegal query string (not an UTF-8 string): ".$sPhrase);
 
 371                                         if (CONST_Debug) var_dump($aPhrase);
 
 374                                 if (trim($aPhrase['string']))
 
 376                                         $aPhrases[$iPhrase] = $aPhrase;
 
 377                                         $aPhrases[$iPhrase]['words'] = explode(' ',$aPhrases[$iPhrase]['string']);
 
 378                                         $aPhrases[$iPhrase]['wordsets'] = getWordSets($aPhrases[$iPhrase]['words'], 0);
 
 379                                         $aTokens = array_merge($aTokens, getTokensFromSets($aPhrases[$iPhrase]['wordsets']));
 
 383                                         unset($aPhrases[$iPhrase]);
 
 387                         // reindex phrases - we make assumptions later on
 
 388                         $aPhraseTypes = array_keys($aPhrases);
 
 389                         $aPhrases = array_values($aPhrases);
 
 391                         if (sizeof($aTokens))
 
 394                                 // Check which tokens we have, get the ID numbers
 
 395                                 $sSQL = 'select word_id,word_token, word, class, type, country_code, operator, search_name_count';
 
 396                                 $sSQL .= ' from word where word_token in ('.join(',',array_map("getDBQuoted",$aTokens)).')';
 
 397                                 //$sSQL .= ' and search_name_count < '.CONST_Max_Word_Frequency;
 
 398                                 //$sSQL .= ' group by word_token, word, class, type, country_code';
 
 400                                 if (CONST_Debug) var_Dump($sSQL);
 
 402                                 $aValidTokens = array();
 
 403                                 if (sizeof($aTokens)) $aDatabaseWords = $oDB->getAll($sSQL);
 
 404                                 else $aDatabaseWords = array();
 
 405                                 if (PEAR::IsError($aDatabaseWords))
 
 407                                         failInternalError("Could not get word tokens.", $sSQL, $aDatabaseWords);
 
 409                                 $aPossibleMainWordIDs = array();
 
 410                                 $aWordFrequencyScores = array();
 
 411                                 foreach($aDatabaseWords as $aToken)
 
 413                                         // Very special case - require 2 letter country param to match the country code found
 
 414                                         if ($bStructuredPhrases && $aToken['country_code'] && !empty($aStructuredQuery['country'])
 
 415                                                         && strlen($aStructuredQuery['country']) == 2 && strtolower($aStructuredQuery['country']) != $aToken['country_code'])
 
 420                                         if (isset($aValidTokens[$aToken['word_token']]))
 
 422                                                 $aValidTokens[$aToken['word_token']][] = $aToken;
 
 426                                                 $aValidTokens[$aToken['word_token']] = array($aToken);
 
 428                                         if (!$aToken['class'] && !$aToken['country_code']) $aPossibleMainWordIDs[$aToken['word_id']] = 1;
 
 429                                         $aWordFrequencyScores[$aToken['word_id']] = $aToken['search_name_count'] + 1;
 
 431                                 if (CONST_Debug) var_Dump($aPhrases, $aValidTokens);
 
 433                                 // Try and calculate GB postcodes we might be missing
 
 434                                 foreach($aTokens as $sToken)
 
 436                                         // Source of gb postcodes is now definitive - always use
 
 437                                         if (preg_match('/^([A-Z][A-Z]?[0-9][0-9A-Z]? ?[0-9])([A-Z][A-Z])$/', strtoupper(trim($sToken)), $aData))
 
 439                                                 if (substr($aData[1],-2,1) != ' ')
 
 441                                                         $aData[0] = substr($aData[0],0,strlen($aData[1]-1)).' '.substr($aData[0],strlen($aData[1]-1));
 
 442                                                         $aData[1] = substr($aData[1],0,-1).' '.substr($aData[1],-1,1);
 
 444                                                 $aGBPostcodeLocation = gbPostcodeCalculate($aData[0], $aData[1], $aData[2], $oDB);
 
 445                                                 if ($aGBPostcodeLocation)
 
 447                                                         $aValidTokens[$sToken] = $aGBPostcodeLocation;
 
 450                                         // US ZIP+4 codes - if there is no token,
 
 451                                         //      merge in the 5-digit ZIP code
 
 452                                         else if (!isset($aValidTokens[$sToken]) && preg_match('/^([0-9]{5}) [0-9]{4}$/', $sToken, $aData))
 
 454                                                 if (isset($aValidTokens[$aData[1]]))
 
 456                                                         foreach($aValidTokens[$aData[1]] as $aToken)
 
 458                                                                 if (!$aToken['class'])
 
 460                                                                         if (isset($aValidTokens[$sToken]))
 
 462                                                                                 $aValidTokens[$sToken][] = $aToken;
 
 466                                                                                 $aValidTokens[$sToken] = array($aToken);
 
 474                                 foreach($aTokens as $sToken)
 
 476                                         // Unknown single word token with a number - assume it is a house number
 
 477                                         if (!isset($aValidTokens[' '.$sToken]) && strpos($sToken,' ') === false && preg_match('/[0-9]/', $sToken))
 
 479                                                 $aValidTokens[' '.$sToken] = array(array('class'=>'place','type'=>'house'));
 
 483                                 // Any words that have failed completely?
 
 486                                 // Start the search process
 
 487                                 $aResultPlaceIDs = array();
 
 490                                    Calculate all searches using aValidTokens i.e.
 
 491                                    'Wodsworth Road, Sheffield' =>
 
 495                                    0      1       (wodsworth)(road)
 
 498                                    Score how good the search is so they can be ordered
 
 500                                 foreach($aPhrases as $iPhrase => $sPhrase)
 
 502                                         $aNewPhraseSearches = array();
 
 503                                         if ($bStructuredPhrases) $sPhraseType = $aPhraseTypes[$iPhrase];
 
 504                                         else $sPhraseType = '';
 
 506                                         foreach($aPhrases[$iPhrase]['wordsets'] as $aWordset)
 
 508                                                 $aWordsetSearches = $aSearches;
 
 510                                                 // Add all words from this wordset
 
 511                                                 foreach($aWordset as $iToken => $sToken)
 
 513                                                         //echo "<br><b>$sToken</b>";
 
 514                                                         $aNewWordsetSearches = array();
 
 516                                                         foreach($aWordsetSearches as $aCurrentSearch)
 
 519                                                                 //var_dump($aCurrentSearch);
 
 522                                                                 // If the token is valid
 
 523                                                                 if (isset($aValidTokens[' '.$sToken]))
 
 525                                                                         foreach($aValidTokens[' '.$sToken] as $aSearchTerm)
 
 527                                                                                 $aSearch = $aCurrentSearch;
 
 528                                                                                 $aSearch['iSearchRank']++;
 
 529                                                                                 if (($sPhraseType == '' || $sPhraseType == 'country') && !empty($aSearchTerm['country_code']) && $aSearchTerm['country_code'] != '0')
 
 531                                                                                         if ($aSearch['sCountryCode'] === false)
 
 533                                                                                                 $aSearch['sCountryCode'] = strtolower($aSearchTerm['country_code']);
 
 534                                                                                                 // Country is almost always at the end of the string - increase score for finding it anywhere else (optimisation)
 
 535                                                                                                 // If reverse order is enabled, it may appear at the beginning as well.
 
 536                                                                                                 if (($iToken+1 != sizeof($aWordset) || $iPhrase+1 != sizeof($aPhrases)) &&
 
 537                                                                                                                 (!$bReverseInPlan || $iToken > 0 || $iPhrase > 0))
 
 539                                                                                                         $aSearch['iSearchRank'] += 5;
 
 541                                                                                                 if ($aSearch['iSearchRank'] < $iMaxRank) $aNewWordsetSearches[] = $aSearch;
 
 544                                                                                 elseif (isset($aSearchTerm['lat']) && $aSearchTerm['lat'] !== '' && $aSearchTerm['lat'] !== null)
 
 546                                                                                         if ($aSearch['fLat'] === '')
 
 548                                                                                                 $aSearch['fLat'] = $aSearchTerm['lat'];
 
 549                                                                                                 $aSearch['fLon'] = $aSearchTerm['lon'];
 
 550                                                                                                 $aSearch['fRadius'] = $aSearchTerm['radius'];
 
 551                                                                                                 if ($aSearch['iSearchRank'] < $iMaxRank) $aNewWordsetSearches[] = $aSearch;
 
 554                                                                                 elseif ($sPhraseType == 'postalcode')
 
 556                                                                                         // 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
 
 557                                                                                         if (isset($aSearchTerm['word_id']) && $aSearchTerm['word_id'])
 
 559                                                                                                 // If we already have a name try putting the postcode first
 
 560                                                                                                 if (sizeof($aSearch['aName']))
 
 562                                                                                                         $aNewSearch = $aSearch;
 
 563                                                                                                         $aNewSearch['aAddress'] = array_merge($aNewSearch['aAddress'], $aNewSearch['aName']);
 
 564                                                                                                         $aNewSearch['aName'] = array();
 
 565                                                                                                         $aNewSearch['aName'][$aSearchTerm['word_id']] = $aSearchTerm['word_id'];
 
 566                                                                                                         if ($aSearch['iSearchRank'] < $iMaxRank) $aNewWordsetSearches[] = $aNewSearch;
 
 569                                                                                                 if (sizeof($aSearch['aName']))
 
 571                                                                                                         if ((!$bStructuredPhrases || $iPhrase > 0) && $sPhraseType != 'country' && (!isset($aValidTokens[$sToken]) || strlen($sToken) < 4 || strpos($sToken, ' ') !== false))
 
 573                                                                                                                 $aSearch['aAddress'][$aSearchTerm['word_id']] = $aSearchTerm['word_id'];
 
 577                                                                                                                 $aCurrentSearch['aFullNameAddress'][$aSearchTerm['word_id']] = $aSearchTerm['word_id'];
 
 578                                                                                                                 $aSearch['iSearchRank'] += 1000; // skip;
 
 583                                                                                                         $aSearch['aName'][$aSearchTerm['word_id']] = $aSearchTerm['word_id'];
 
 584                                                                                                         //$aSearch['iNamePhrase'] = $iPhrase;
 
 586                                                                                                 if ($aSearch['iSearchRank'] < $iMaxRank) $aNewWordsetSearches[] = $aSearch;
 
 590                                                                                 elseif (($sPhraseType == '' || $sPhraseType == 'street') && $aSearchTerm['class'] == 'place' && $aSearchTerm['type'] == 'house')
 
 592                                                                                         if ($aSearch['sHouseNumber'] === '')
 
 594                                                                                                 $aSearch['sHouseNumber'] = $sToken;
 
 595                                                                                                 if ($aSearch['iSearchRank'] < $iMaxRank) $aNewWordsetSearches[] = $aSearch;
 
 597                                                                                                 // Fall back to not searching for this item (better than nothing)
 
 598                                                                                                 $aSearch = $aCurrentSearch;
 
 599                                                                                                 $aSearch['iSearchRank'] += 1;
 
 600                                                                                                 if ($aSearch['iSearchRank'] < $iMaxRank) $aNewWordsetSearches[] = $aSearch;
 
 604                                                                                 elseif ($sPhraseType == '' && $aSearchTerm['class'] !== '' && $aSearchTerm['class'] !== null)
 
 606                                                                                         if ($aSearch['sClass'] === '')
 
 608                                                                                                 $aSearch['sOperator'] = $aSearchTerm['operator'];
 
 609                                                                                                 $aSearch['sClass'] = $aSearchTerm['class'];
 
 610                                                                                                 $aSearch['sType'] = $aSearchTerm['type'];
 
 611                                                                                                 if (sizeof($aSearch['aName'])) $aSearch['sOperator'] = 'name';
 
 612                                                                                                 else $aSearch['sOperator'] = 'near'; // near = in for the moment
 
 614                                                                                                 // Do we have a shortcut id?
 
 615                                                                                                 if ($aSearch['sOperator'] == 'name')
 
 617                                                                                                         $sSQL = "select get_tagpair('".$aSearch['sClass']."', '".$aSearch['sType']."')";
 
 618                                                                                                         if ($iAmenityID = $oDB->getOne($sSQL))
 
 620                                                                                                                 $aValidTokens[$aSearch['sClass'].':'.$aSearch['sType']] = array('word_id' => $iAmenityID);
 
 621                                                                                                                 $aSearch['aName'][$iAmenityID] = $iAmenityID;
 
 622                                                                                                                 $aSearch['sClass'] = '';
 
 623                                                                                                                 $aSearch['sType'] = '';
 
 626                                                                                                 if ($aSearch['iSearchRank'] < $iMaxRank) $aNewWordsetSearches[] = $aSearch;
 
 629                                                                                 elseif (isset($aSearchTerm['word_id']) && $aSearchTerm['word_id'])
 
 631                                                                                         if (sizeof($aSearch['aName']))
 
 633                                                                                                 if ((!$bStructuredPhrases || $iPhrase > 0) && $sPhraseType != 'country' && (!isset($aValidTokens[$sToken]) || strlen($sToken) < 4 || strpos($sToken, ' ') !== false))
 
 635                                                                                                         $aSearch['aAddress'][$aSearchTerm['word_id']] = $aSearchTerm['word_id'];
 
 639                                                                                                         $aCurrentSearch['aFullNameAddress'][$aSearchTerm['word_id']] = $aSearchTerm['word_id'];
 
 640                                                                                                         $aSearch['iSearchRank'] += 1000; // skip;
 
 645                                                                                                 $aSearch['aName'][$aSearchTerm['word_id']] = $aSearchTerm['word_id'];
 
 646                                                                                                 //$aSearch['iNamePhrase'] = $iPhrase;
 
 648                                                                                         if ($aSearch['iSearchRank'] < $iMaxRank) $aNewWordsetSearches[] = $aSearch;
 
 652                                                                 if (isset($aValidTokens[$sToken]))
 
 654                                                                         // Allow searching for a word - but at extra cost
 
 655                                                                         foreach($aValidTokens[$sToken] as $aSearchTerm)
 
 657                                                                                 if (isset($aSearchTerm['word_id']) && $aSearchTerm['word_id'])
 
 659                                                                                         if ((!$bStructuredPhrases || $iPhrase > 0) && sizeof($aCurrentSearch['aName']) && strlen($sToken) >= 4)
 
 661                                                                                                 $aSearch = $aCurrentSearch;
 
 662                                                                                                 $aSearch['iSearchRank'] += 1;
 
 663                                                                                                 if ($aWordFrequencyScores[$aSearchTerm['word_id']] < CONST_Max_Word_Frequency)
 
 665                                                                                                         $aSearch['aAddress'][$aSearchTerm['word_id']] = $aSearchTerm['word_id'];
 
 666                                                                                                         if ($aSearch['iSearchRank'] < $iMaxRank) $aNewWordsetSearches[] = $aSearch;
 
 668                                                                                                 elseif (isset($aValidTokens[' '.$sToken])) // revert to the token version?
 
 670                                                                                                         foreach($aValidTokens[' '.$sToken] as $aSearchTermToken)
 
 672                                                                                                                 if (empty($aSearchTermToken['country_code'])
 
 673                                                                                                                                 && empty($aSearchTermToken['lat'])
 
 674                                                                                                                                 && empty($aSearchTermToken['class']))
 
 676                                                                                                                         $aSearch = $aCurrentSearch;
 
 677                                                                                                                         $aSearch['iSearchRank'] += 1;
 
 678                                                                                                                         $aSearch['aAddress'][$aSearchTermToken['word_id']] = $aSearchTermToken['word_id'];
 
 679                                                                                                                         if ($aSearch['iSearchRank'] < $iMaxRank) $aNewWordsetSearches[] = $aSearch;
 
 685                                                                                                         $aSearch['aAddressNonSearch'][$aSearchTerm['word_id']] = $aSearchTerm['word_id'];
 
 686                                                                                                         if ($aSearch['iSearchRank'] < $iMaxRank) $aNewWordsetSearches[] = $aSearch;
 
 690                                                                                         if (!sizeof($aCurrentSearch['aName']) || $aCurrentSearch['iNamePhrase'] == $iPhrase)
 
 692                                                                                                 $aSearch = $aCurrentSearch;
 
 693                                                                                                 $aSearch['iSearchRank'] += 2;
 
 694                                                                                                 if (preg_match('#^[0-9]+$#', $sToken)) $aSearch['iSearchRank'] += 2;
 
 695                                                                                                 if ($aWordFrequencyScores[$aSearchTerm['word_id']] < CONST_Max_Word_Frequency)
 
 696                                                                                                         $aSearch['aName'][$aSearchTerm['word_id']] = $aSearchTerm['word_id'];
 
 698                                                                                                         $aSearch['aNameNonSearch'][$aSearchTerm['word_id']] = $aSearchTerm['word_id'];
 
 699                                                                                                 $aSearch['iNamePhrase'] = $iPhrase;
 
 700                                                                                                 if ($aSearch['iSearchRank'] < $iMaxRank) $aNewWordsetSearches[] = $aSearch;
 
 707                                                                         // Allow skipping a word - but at EXTREAM cost
 
 708                                                                         //$aSearch = $aCurrentSearch;
 
 709                                                                         //$aSearch['iSearchRank']+=100;
 
 710                                                                         //$aNewWordsetSearches[] = $aSearch;
 
 714                                                         usort($aNewWordsetSearches, 'bySearchRank');
 
 715                                                         $aWordsetSearches = array_slice($aNewWordsetSearches, 0, 50);
 
 717                                                 //var_Dump('<hr>',sizeof($aWordsetSearches)); exit;
 
 719                                                 $aNewPhraseSearches = array_merge($aNewPhraseSearches, $aNewWordsetSearches);
 
 720                                                 usort($aNewPhraseSearches, 'bySearchRank');
 
 722                                                 $aSearchHash = array();
 
 723                                                 foreach($aNewPhraseSearches as $iSearch => $aSearch)
 
 725                                                         $sHash = serialize($aSearch);
 
 726                                                         if (isset($aSearchHash[$sHash])) unset($aNewPhraseSearches[$iSearch]);
 
 727                                                         else $aSearchHash[$sHash] = 1;
 
 730                                                 $aNewPhraseSearches = array_slice($aNewPhraseSearches, 0, 50);
 
 733                                         // Re-group the searches by their score, junk anything over 20 as just not worth trying
 
 734                                         $aGroupedSearches = array();
 
 735                                         foreach($aNewPhraseSearches as $aSearch)
 
 737                                                 if ($aSearch['iSearchRank'] < $iMaxRank)
 
 739                                                         if (!isset($aGroupedSearches[$aSearch['iSearchRank']])) $aGroupedSearches[$aSearch['iSearchRank']] = array();
 
 740                                                         $aGroupedSearches[$aSearch['iSearchRank']][] = $aSearch;
 
 743                                         ksort($aGroupedSearches);
 
 746                                         $aSearches = array();
 
 747                                         foreach($aGroupedSearches as $iScore => $aNewSearches)
 
 749                                                 $iSearchCount += sizeof($aNewSearches);
 
 750                                                 $aSearches = array_merge($aSearches, $aNewSearches);
 
 751                                                 if ($iSearchCount > 50) break;
 
 754                                         //if (CONST_Debug) _debugDumpGroupedSearches($aGroupedSearches, $aValidTokens);
 
 761                                 // Re-group the searches by their score, junk anything over 20 as just not worth trying
 
 762                                 $aGroupedSearches = array();
 
 763                                 foreach($aSearches as $aSearch)
 
 765                                         if ($aSearch['iSearchRank'] < $iMaxRank)
 
 767                                                 if (!isset($aGroupedSearches[$aSearch['iSearchRank']])) $aGroupedSearches[$aSearch['iSearchRank']] = array();
 
 768                                                 $aGroupedSearches[$aSearch['iSearchRank']][] = $aSearch;
 
 771                                 ksort($aGroupedSearches);
 
 774                         if (CONST_Debug) var_Dump($aGroupedSearches);
 
 778                                 $aCopyGroupedSearches = $aGroupedSearches;
 
 779                                 foreach($aCopyGroupedSearches as $iGroup => $aSearches)
 
 781                                         foreach($aSearches as $iSearch => $aSearch)
 
 783                                                 if (sizeof($aSearch['aAddress']))
 
 785                                                         $iReverseItem = array_pop($aSearch['aAddress']);
 
 786                                                         if (isset($aPossibleMainWordIDs[$iReverseItem]))
 
 788                                                                 $aSearch['aAddress'] = array_merge($aSearch['aAddress'], $aSearch['aName']);
 
 789                                                                 $aSearch['aName'] = array($iReverseItem);
 
 790                                                                 $aGroupedSearches[$iGroup][] = $aSearch;
 
 792                                                         //$aReverseSearch['aName'][$iReverseItem] = $iReverseItem;
 
 793                                                         //$aGroupedSearches[$iGroup][] = $aReverseSearch;
 
 799                         if (CONST_Search_TryDroppedAddressTerms && sizeof($aStructuredQuery) > 0)
 
 801                                 $aCopyGroupedSearches = $aGroupedSearches;
 
 802                                 foreach($aCopyGroupedSearches as $iGroup => $aSearches)
 
 804                                         foreach($aSearches as $iSearch => $aSearch)
 
 806                                                 $aReductionsList = array($aSearch['aAddress']);
 
 807                                                 $iSearchRank = $aSearch['iSearchRank'];
 
 808                                                 while(sizeof($aReductionsList) > 0)
 
 811                                                         if ($iSearchRank > iMaxRank) break 3;
 
 812                                                         $aNewReductionsList = array();
 
 813                                                         foreach($aReductionsList as $aReductionsWordList)
 
 815                                                                 for ($iReductionWord = 0; $iReductionWord < sizeof($aReductionsWordList); $iReductionWord++)
 
 817                                                                         $aReductionsWordListResult = array_merge(array_slice($aReductionsWordList, 0, $iReductionWord), array_slice($aReductionsWordList, $iReductionWord+1));
 
 818                                                                         $aReverseSearch = $aSearch;
 
 819                                                                         $aSearch['aAddress'] = $aReductionsWordListResult;
 
 820                                                                         $aSearch['iSearchRank'] = $iSearchRank;
 
 821                                                                         $aGroupedSearches[$iSearchRank][] = $aReverseSearch;
 
 822                                                                         if (sizeof($aReductionsWordListResult) > 0)
 
 824                                                                                 $aNewReductionsList[] = $aReductionsWordListResult;
 
 828                                                         $aReductionsList = $aNewReductionsList;
 
 832                                 ksort($aGroupedSearches);
 
 835                         // Filter out duplicate searches
 
 836                         $aSearchHash = array();
 
 837                         foreach($aGroupedSearches as $iGroup => $aSearches)
 
 839                                 foreach($aSearches as $iSearch => $aSearch)
 
 841                                         $sHash = serialize($aSearch);
 
 842                                         if (isset($aSearchHash[$sHash]))
 
 844                                                 unset($aGroupedSearches[$iGroup][$iSearch]);
 
 845                                                 if (sizeof($aGroupedSearches[$iGroup]) == 0) unset($aGroupedSearches[$iGroup]);
 
 849                                                 $aSearchHash[$sHash] = 1;
 
 854                         if (CONST_Debug) _debugDumpGroupedSearches($aGroupedSearches, $aValidTokens);
 
 858                         foreach($aGroupedSearches as $iGroupedRank => $aSearches)
 
 861                                 foreach($aSearches as $aSearch)
 
 865                                         if (CONST_Debug) { echo "<hr><b>Search Loop, group $iGroupLoop, loop $iQueryLoop</b>"; }
 
 866                                         if (CONST_Debug) _debugDumpGroupedSearches(array($iGroupedRank => array($aSearch)), $aValidTokens);
 
 869                                         // Must have a location term
 
 870                                         if (!sizeof($aSearch['aName']) && !sizeof($aSearch['aAddress']) && !$aSearch['fLon'])
 
 872                                                 if ($aSearch['sCountryCode'] && !$aSearch['sClass'] && !$aSearch['sHouseNumber'])
 
 874                                                         if (4 >= $iMinAddressRank && 4 <= $iMaxAddressRank)
 
 876                                                                 $sSQL = "select place_id from placex where calculated_country_code='".$aSearch['sCountryCode']."' and rank_search = 4";
 
 877                                                                 if ($sCountryCodesSQL) $sSQL .= " and calculated_country_code in ($sCountryCodesSQL)";
 
 878                                                                 $sSQL .= " order by st_area(geometry) desc limit 1";
 
 879                                                                 if (CONST_Debug) var_dump($sSQL);
 
 880                                                                 $aPlaceIDs = $oDB->getCol($sSQL);
 
 885                                                         if (!$bBoundingBoxSearch && !$aSearch['fLon']) continue;
 
 886                                                         if (!$aSearch['sClass']) continue;
 
 887                                                         $sSQL = "select count(*) from pg_tables where tablename = 'place_classtype_".$aSearch['sClass']."_".$aSearch['sType']."'";
 
 888                                                         if ($oDB->getOne($sSQL))
 
 890                                                                 $sSQL = "select place_id from place_classtype_".$aSearch['sClass']."_".$aSearch['sType']." ct";
 
 891                                                                 if ($sCountryCodesSQL) $sSQL .= " join placex using (place_id)";
 
 892                                                                 $sSQL .= " where st_contains($sViewboxSmallSQL, ct.centroid)";
 
 893                                                                 if ($sCountryCodesSQL) $sSQL .= " and calculated_country_code in ($sCountryCodesSQL)";
 
 894                                                                 if (sizeof($aExcludePlaceIDs))
 
 896                                                                         $sSQL .= " and place_id not in (".join(',',$aExcludePlaceIDs).")";
 
 898                                                                 if ($sViewboxCentreSQL) $sSQL .= " order by st_distance($sViewboxCentreSQL, ct.centroid) asc";
 
 899                                                                 $sSQL .= " limit $iLimit";
 
 900                                                                 if (CONST_Debug) var_dump($sSQL);
 
 901                                                                 $aPlaceIDs = $oDB->getCol($sSQL);
 
 903                                                                 // If excluded place IDs are given, it is fair to assume that
 
 904                                                                 // there have been results in the small box, so no further
 
 905                                                                 // expansion in that case.
 
 906                                                                 if (!sizeof($aPlaceIDs) && !sizeof($aExcludePlaceIDs))
 
 908                                                                         $sSQL = "select place_id from place_classtype_".$aSearch['sClass']."_".$aSearch['sType']." ct";
 
 909                                                                         if ($sCountryCodesSQL) $sSQL .= " join placex using (place_id)";
 
 910                                                                         $sSQL .= " where st_contains($sViewboxLargeSQL, ct.centroid)";
 
 911                                                                         if ($sCountryCodesSQL) $sSQL .= " and calculated_country_code in ($sCountryCodesSQL)";
 
 912                                                                         if ($sViewboxCentreSQL) $sSQL .= " order by st_distance($sViewboxCentreSQL, ct.centroid) asc";
 
 913                                                                         $sSQL .= " limit $iLimit";
 
 914                                                                         if (CONST_Debug) var_dump($sSQL);
 
 915                                                                         $aPlaceIDs = $oDB->getCol($sSQL);
 
 920                                                                 $sSQL = "select place_id from placex where class='".$aSearch['sClass']."' and type='".$aSearch['sType']."'";
 
 921                                                                 $sSQL .= " and st_contains($sViewboxSmallSQL, geometry) and linked_place_id is null";
 
 922                                                                 if ($sCountryCodesSQL) $sSQL .= " and calculated_country_code in ($sCountryCodesSQL)";
 
 923                                                                 if ($sViewboxCentreSQL) $sSQL .= " order by st_distance($sViewboxCentreSQL, centroid) asc";
 
 924                                                                 $sSQL .= " limit $iLimit";
 
 925                                                                 if (CONST_Debug) var_dump($sSQL);
 
 926                                                                 $aPlaceIDs = $oDB->getCol($sSQL);
 
 932                                                 $aPlaceIDs = array();
 
 934                                                 // First we need a position, either aName or fLat or both
 
 938                                                 // TODO: filter out the pointless search terms (2 letter name tokens and less)
 
 939                                                 // they might be right - but they are just too darned expensive to run
 
 940                                                 if (sizeof($aSearch['aName'])) $aTerms[] = "name_vector @> ARRAY[".join($aSearch['aName'],",")."]";
 
 941                                                 if (sizeof($aSearch['aNameNonSearch'])) $aTerms[] = "array_cat(name_vector,ARRAY[]::integer[]) @> ARRAY[".join($aSearch['aNameNonSearch'],",")."]";
 
 942                                                 if (sizeof($aSearch['aAddress']) && $aSearch['aName'] != $aSearch['aAddress'])
 
 944                                                         // For infrequent name terms disable index usage for address
 
 945                                                         if (CONST_Search_NameOnlySearchFrequencyThreshold &&
 
 946                                                                         sizeof($aSearch['aName']) == 1 &&
 
 947                                                                         $aWordFrequencyScores[$aSearch['aName'][reset($aSearch['aName'])]] < CONST_Search_NameOnlySearchFrequencyThreshold)
 
 949                                                                 $aTerms[] = "array_cat(nameaddress_vector,ARRAY[]::integer[]) @> ARRAY[".join(array_merge($aSearch['aAddress'],$aSearch['aAddressNonSearch']),",")."]";
 
 953                                                                 $aTerms[] = "nameaddress_vector @> ARRAY[".join($aSearch['aAddress'],",")."]";
 
 954                                                                 if (sizeof($aSearch['aAddressNonSearch'])) $aTerms[] = "array_cat(nameaddress_vector,ARRAY[]::integer[]) @> ARRAY[".join($aSearch['aAddressNonSearch'],",")."]";
 
 957                                                 if ($aSearch['sCountryCode']) $aTerms[] = "country_code = '".pg_escape_string($aSearch['sCountryCode'])."'";
 
 958                                                 if ($aSearch['sHouseNumber']) $aTerms[] = "address_rank between 16 and 27";
 
 959                                                 if ($aSearch['fLon'] && $aSearch['fLat'])
 
 961                                                         $aTerms[] = "ST_DWithin(centroid, ST_SetSRID(ST_Point(".$aSearch['fLon'].",".$aSearch['fLat']."),4326), ".$aSearch['fRadius'].")";
 
 962                                                         $aOrder[] = "ST_Distance(centroid, ST_SetSRID(ST_Point(".$aSearch['fLon'].",".$aSearch['fLat']."),4326)) ASC";
 
 964                                                 if (sizeof($aExcludePlaceIDs))
 
 966                                                         $aTerms[] = "place_id not in (".join(',',$aExcludePlaceIDs).")";
 
 968                                                 if ($sCountryCodesSQL)
 
 970                                                         $aTerms[] = "country_code in ($sCountryCodesSQL)";
 
 973                                                 if ($bBoundingBoxSearch) $aTerms[] = "centroid && $sViewboxSmallSQL";
 
 974                                                 if ($sNearPointSQL) $aOrder[] = "ST_Distance($sNearPointSQL, centroid) asc";
 
 976                                                 $sImportanceSQL = '(case when importance = 0 OR importance IS NULL then 0.75-(search_rank::float/40) else importance end)';
 
 977                                                 if ($sViewboxSmallSQL) $sImportanceSQL .= " * case when ST_Contains($sViewboxSmallSQL, centroid) THEN 1 ELSE 0.5 END";
 
 978                                                 if ($sViewboxLargeSQL) $sImportanceSQL .= " * case when ST_Contains($sViewboxLargeSQL, centroid) THEN 1 ELSE 0.5 END";
 
 979                                                 $aOrder[] = "$sImportanceSQL DESC";
 
 980                                                 if (sizeof($aSearch['aFullNameAddress']))
 
 982                                                         $aOrder[] = '(select count(*) from (select unnest(ARRAY['.join($aSearch['aFullNameAddress'],",").']) INTERSECT select unnest(nameaddress_vector))s) DESC';
 
 987                                                         $sSQL = "select place_id";
 
 988                                                         $sSQL .= " from search_name";
 
 989                                                         $sSQL .= " where ".join(' and ',$aTerms);
 
 990                                                         $sSQL .= " order by ".join(', ',$aOrder);
 
 991                                                         if ($aSearch['sHouseNumber'] || $aSearch['sClass'])
 
 992                                                                 $sSQL .= " limit 50";
 
 993                                                         elseif (!sizeof($aSearch['aName']) && !sizeof($aSearch['aAddress']) && $aSearch['sClass'])
 
 996                                                                 $sSQL .= " limit ".$iLimit;
 
 998                                                         if (CONST_Debug) { var_dump($sSQL); }
 
 999                                                         $aViewBoxPlaceIDs = $oDB->getAll($sSQL);
 
1000                                                         if (PEAR::IsError($aViewBoxPlaceIDs))
 
1002                                                                 failInternalError("Could not get places for search terms.", $sSQL, $aViewBoxPlaceIDs);
 
1004                                                         //var_dump($aViewBoxPlaceIDs);
 
1005                                                         // Did we have an viewbox matches?
 
1006                                                         $aPlaceIDs = array();
 
1007                                                         $bViewBoxMatch = false;
 
1008                                                         foreach($aViewBoxPlaceIDs as $aViewBoxRow)
 
1010                                                                 //if ($bViewBoxMatch == 1 && $aViewBoxRow['in_small'] == 'f') break;
 
1011                                                                 //if ($bViewBoxMatch == 2 && $aViewBoxRow['in_large'] == 'f') break;
 
1012                                                                 //if ($aViewBoxRow['in_small'] == 't') $bViewBoxMatch = 1;
 
1013                                                                 //else if ($aViewBoxRow['in_large'] == 't') $bViewBoxMatch = 2;
 
1014                                                                 $aPlaceIDs[] = $aViewBoxRow['place_id'];
 
1017                                                 //var_Dump($aPlaceIDs);
 
1020                                                 if ($aSearch['sHouseNumber'] && sizeof($aPlaceIDs))
 
1022                                                         $aRoadPlaceIDs = $aPlaceIDs;
 
1023                                                         $sPlaceIDs = join(',',$aPlaceIDs);
 
1025                                                         // Now they are indexed look for a house attached to a street we found
 
1026                                                         $sHouseNumberRegex = '\\\\m'.str_replace(' ','[-,/ ]',$aSearch['sHouseNumber']).'\\\\M';
 
1027                                                         $sSQL = "select place_id from placex where parent_place_id in (".$sPlaceIDs.") and housenumber ~* E'".$sHouseNumberRegex."'";
 
1028                                                         if (sizeof($aExcludePlaceIDs))
 
1030                                                                 $sSQL .= " and place_id not in (".join(',',$aExcludePlaceIDs).")";
 
1032                                                         $sSQL .= " limit $iLimit";
 
1033                                                         if (CONST_Debug) var_dump($sSQL);
 
1034                                                         $aPlaceIDs = $oDB->getCol($sSQL);
 
1036                                                         // If not try the aux fallback table
 
1037                                                         if (!sizeof($aPlaceIDs))
 
1039                                                                 $sSQL = "select place_id from location_property_aux where parent_place_id in (".$sPlaceIDs.") and housenumber = '".pg_escape_string($aSearch['sHouseNumber'])."'";
 
1040                                                                 if (sizeof($aExcludePlaceIDs))
 
1042                                                                         $sSQL .= " and place_id not in (".join(',',$aExcludePlaceIDs).")";
 
1044                                                                 //$sSQL .= " limit $iLimit";
 
1045                                                                 if (CONST_Debug) var_dump($sSQL);
 
1046                                                                 $aPlaceIDs = $oDB->getCol($sSQL);
 
1049                                                         if (!sizeof($aPlaceIDs))
 
1051                                                                 $sSQL = "select place_id from location_property_tiger where parent_place_id in (".$sPlaceIDs.") and housenumber = '".pg_escape_string($aSearch['sHouseNumber'])."'";
 
1052                                                                 if (sizeof($aExcludePlaceIDs))
 
1054                                                                         $sSQL .= " and place_id not in (".join(',',$aExcludePlaceIDs).")";
 
1056                                                                 //$sSQL .= " limit $iLimit";
 
1057                                                                 if (CONST_Debug) var_dump($sSQL);
 
1058                                                                 $aPlaceIDs = $oDB->getCol($sSQL);
 
1061                                                         // Fallback to the road
 
1062                                                         if (!sizeof($aPlaceIDs) && preg_match('/[0-9]+/', $aSearch['sHouseNumber']))
 
1064                                                                 $aPlaceIDs = $aRoadPlaceIDs;
 
1069                                                 if ($aSearch['sClass'] && sizeof($aPlaceIDs))
 
1071                                                         $sPlaceIDs = join(',',$aPlaceIDs);
 
1072                                                         $aClassPlaceIDs = array();
 
1074                                                         if (!$aSearch['sOperator'] || $aSearch['sOperator'] == 'name')
 
1076                                                                 // If they were searching for a named class (i.e. 'Kings Head pub') then we might have an extra match
 
1077                                                                 $sSQL = "select place_id from placex where place_id in ($sPlaceIDs) and class='".$aSearch['sClass']."' and type='".$aSearch['sType']."'";
 
1078                                                                 $sSQL .= " and linked_place_id is null";
 
1079                                                                 if ($sCountryCodesSQL) $sSQL .= " and calculated_country_code in ($sCountryCodesSQL)";
 
1080                                                                 $sSQL .= " order by rank_search asc limit $iLimit";
 
1081                                                                 if (CONST_Debug) var_dump($sSQL);
 
1082                                                                 $aClassPlaceIDs = $oDB->getCol($sSQL);
 
1085                                                         if (!$aSearch['sOperator'] || $aSearch['sOperator'] == 'near') // & in
 
1087                                                                 $sSQL = "select count(*) from pg_tables where tablename = 'place_classtype_".$aSearch['sClass']."_".$aSearch['sType']."'";
 
1088                                                                 $bCacheTable = $oDB->getOne($sSQL);
 
1090                                                                 $sSQL = "select min(rank_search) from placex where place_id in ($sPlaceIDs)";
 
1092                                                                 if (CONST_Debug) var_dump($sSQL);
 
1093                                                                 $iMaxRank = ((int)$oDB->getOne($sSQL));
 
1095                                                                 // For state / country level searches the normal radius search doesn't work very well
 
1096                                                                 $sPlaceGeom = false;
 
1097                                                                 if ($iMaxRank < 9 && $bCacheTable)
 
1099                                                                         // Try and get a polygon to search in instead
 
1100                                                                         $sSQL = "select geometry from placex where place_id in ($sPlaceIDs) and rank_search < $iMaxRank + 5 and st_geometrytype(geometry) in ('ST_Polygon','ST_MultiPolygon') order by rank_search asc limit 1";
 
1101                                                                         if (CONST_Debug) var_dump($sSQL);
 
1102                                                                         $sPlaceGeom = $oDB->getOne($sSQL);
 
1112                                                                         $sSQL = "select place_id from placex where place_id in ($sPlaceIDs) and rank_search < $iMaxRank";
 
1113                                                                         if (CONST_Debug) var_dump($sSQL);
 
1114                                                                         $aPlaceIDs = $oDB->getCol($sSQL);
 
1115                                                                         $sPlaceIDs = join(',',$aPlaceIDs);
 
1118                                                                 if ($sPlaceIDs || $sPlaceGeom)
 
1124                                                                                 // More efficient - can make the range bigger
 
1128                                                                                 if ($sNearPointSQL) $sOrderBySQL = "ST_Distance($sNearPointSQL, l.centroid)";
 
1129                                                                                 else if ($sPlaceIDs) $sOrderBySQL = "ST_Distance(l.centroid, f.geometry)";
 
1130                                                                                 else if ($sPlaceGeom) $sOrderBysSQL = "ST_Distance(st_centroid('".$sPlaceGeom."'), l.centroid)";
 
1132                                                                                 $sSQL = "select distinct l.place_id".($sOrderBySQL?','.$sOrderBySQL:'')." from place_classtype_".$aSearch['sClass']."_".$aSearch['sType']." as l";
 
1133                                                                                 if ($sCountryCodesSQL) $sSQL .= " join placex as lp using (place_id)";
 
1136                                                                                         $sSQL .= ",placex as f where ";
 
1137                                                                                         $sSQL .= "f.place_id in ($sPlaceIDs) and ST_DWithin(l.centroid, f.centroid, $fRange) ";
 
1142                                                                                         $sSQL .= "ST_Contains('".$sPlaceGeom."', l.centroid) ";
 
1144                                                                                 if (sizeof($aExcludePlaceIDs))
 
1146                                                                                         $sSQL .= " and l.place_id not in (".join(',',$aExcludePlaceIDs).")";
 
1148                                                                                 if ($sCountryCodesSQL) $sSQL .= " and lp.calculated_country_code in ($sCountryCodesSQL)";
 
1149                                                                                 if ($sOrderBySQL) $sSQL .= "order by ".$sOrderBySQL." asc";
 
1150                                                                                 if ($iOffset) $sSQL .= " offset $iOffset";
 
1151                                                                                 $sSQL .= " limit $iLimit";
 
1152                                                                                 if (CONST_Debug) var_dump($sSQL);
 
1153                                                                                 $aClassPlaceIDs = array_merge($aClassPlaceIDs, $oDB->getCol($sSQL));
 
1157                                                                                 if (isset($aSearch['fRadius']) && $aSearch['fRadius']) $fRange = $aSearch['fRadius'];
 
1160                                                                                 if ($sNearPointSQL) $sOrderBySQL = "ST_Distance($sNearPointSQL, l.geometry)";
 
1161                                                                                 else $sOrderBySQL = "ST_Distance(l.geometry, f.geometry)";
 
1163                                                                                 $sSQL = "select distinct l.place_id".($sOrderBysSQL?','.$sOrderBysSQL:'')." from placex as l,placex as f where ";
 
1164                                                                                 $sSQL .= "f.place_id in ( $sPlaceIDs) and ST_DWithin(l.geometry, f.centroid, $fRange) ";
 
1165                                                                                 $sSQL .= "and l.class='".$aSearch['sClass']."' and l.type='".$aSearch['sType']."' ";
 
1166                                                                                 if (sizeof($aExcludePlaceIDs))
 
1168                                                                                         $sSQL .= " and l.place_id not in (".join(',',$aExcludePlaceIDs).")";
 
1170                                                                                 if ($sCountryCodesSQL) $sSQL .= " and l.calculated_country_code in ($sCountryCodesSQL)";
 
1171                                                                                 if ($sOrderBy) $sSQL .= "order by ".$OrderBysSQL." asc";
 
1172                                                                                 if ($iOffset) $sSQL .= " offset $iOffset";
 
1173                                                                                 $sSQL .= " limit $iLimit";
 
1174                                                                                 if (CONST_Debug) var_dump($sSQL);
 
1175                                                                                 $aClassPlaceIDs = array_merge($aClassPlaceIDs, $oDB->getCol($sSQL));
 
1180                                                         $aPlaceIDs = $aClassPlaceIDs;
 
1186                                         if (PEAR::IsError($aPlaceIDs))
 
1188                                                 failInternalError("Could not get place IDs from tokens." ,$sSQL, $aPlaceIDs);
 
1191                                         if (CONST_Debug) { echo "<br><b>Place IDs:</b> "; var_Dump($aPlaceIDs); }
 
1193                                         foreach($aPlaceIDs as $iPlaceID)
 
1195                                                 $aResultPlaceIDs[$iPlaceID] = $iPlaceID;
 
1197                                         if ($iQueryLoop > 20) break;
 
1200                                 if (isset($aResultPlaceIDs) && sizeof($aResultPlaceIDs) && ($iMinAddressRank != 0 || $iMaxAddressRank != 30))
 
1202                                         // Need to verify passes rank limits before dropping out of the loop (yuk!)
 
1203                                         $sSQL = "select place_id from placex where place_id in (".join(',',$aResultPlaceIDs).") ";
 
1204                                         $sSQL .= "and (placex.rank_address between $iMinAddressRank and $iMaxAddressRank ";
 
1205                                         if (14 >= $iMinAddressRank && 14 <= $iMaxAddressRank) $sSQL .= " OR (extratags->'place') = 'city'";
 
1206                                         if ($aAddressRankList) $sSQL .= " OR placex.rank_address in (".join(',',$aAddressRankList).")";
 
1207                                         $sSQL .= ") UNION select place_id from location_property_tiger where place_id in (".join(',',$aResultPlaceIDs).") ";
 
1208                                         $sSQL .= "and (30 between $iMinAddressRank and $iMaxAddressRank ";
 
1209                                         if ($aAddressRankList) $sSQL .= " OR 30 in (".join(',',$aAddressRankList).")";
 
1211                                         if (CONST_Debug) var_dump($sSQL);
 
1212                                         $aResultPlaceIDs = $oDB->getCol($sSQL);
 
1217                                 if (isset($aResultPlaceIDs) && sizeof($aResultPlaceIDs)) break;
 
1218                                 if ($iGroupLoop > 4) break;
 
1219                                 if ($iQueryLoop > 30) break;
 
1222                         // Did we find anything?
 
1223                         if (isset($aResultPlaceIDs) && sizeof($aResultPlaceIDs))
 
1225                                 //var_Dump($aResultPlaceIDs);exit;
 
1226                                 // Get the details for display (is this a redundant extra step?)
 
1227                                 $sPlaceIDs = join(',',$aResultPlaceIDs);
 
1228                                 $sOrderSQL = 'CASE ';
 
1229                                 foreach(array_keys($aResultPlaceIDs) as $iOrder => $iPlaceID)
 
1231                                         $sOrderSQL .= 'when min(place_id) = '.$iPlaceID.' then '.$iOrder.' ';
 
1233                                 $sOrderSQL .= ' ELSE 10000000 END';
 
1234                                 $sSQL = "select osm_type,osm_id,class,type,admin_level,rank_search,rank_address,min(place_id) as place_id,calculated_country_code as country_code,";
 
1235                                 $sSQL .= "get_address_by_language(place_id, $sLanguagePrefArraySQL) as langaddress,";
 
1236                                 $sSQL .= "get_name_by_language(name, $sLanguagePrefArraySQL) as placename,";
 
1237                                 $sSQL .= "get_name_by_language(name, ARRAY['ref']) as ref,";
 
1238                                 $sSQL .= "avg(ST_X(centroid)) as lon,avg(ST_Y(centroid)) as lat, ";
 
1239                                 //$sSQL .= $sOrderSQL." as porder, ";
 
1240                                 $sSQL .= "coalesce(importance,0.75-(rank_search::float/40)) as importance, ";
 
1241                                 $sSQL .= "(select max(p.importance*(p.rank_address+2)) from place_addressline s, placex p where s.place_id = min(placex.place_id) and p.place_id = s.address_place_id and s.isaddress and p.importance is not null) as addressimportance, ";
 
1242                                 $sSQL .= "(extratags->'place') as extra_place ";
 
1243                                 $sSQL .= "from placex where place_id in ($sPlaceIDs) ";
 
1244                                 $sSQL .= "and (placex.rank_address between $iMinAddressRank and $iMaxAddressRank ";
 
1245                                 if (14 >= $iMinAddressRank && 14 <= $iMaxAddressRank) $sSQL .= " OR (extratags->'place') = 'city'";
 
1246                                 if ($aAddressRankList) $sSQL .= " OR placex.rank_address in (".join(',',$aAddressRankList).")";
 
1248                                 if ($sAllowedTypesSQLList) $sSQL .= "and placex.class in $sAllowedTypesSQLList ";
 
1249                                 $sSQL .= "and linked_place_id is null ";
 
1250                                 $sSQL .= "group by osm_type,osm_id,class,type,admin_level,rank_search,rank_address,calculated_country_code,importance";
 
1251                                 if (!$bDeDupe) $sSQL .= ",place_id";
 
1252                                 $sSQL .= ",langaddress ";
 
1253                                 $sSQL .= ",placename ";
 
1255                                 $sSQL .= ",extratags->'place' ";
 
1257                                 $sSQL .= "select 'T' as osm_type,place_id as osm_id,'place' as class,'house' as type,null as admin_level,30 as rank_search,30 as rank_address,min(place_id) as place_id,'us' as country_code,";
 
1258                                 $sSQL .= "get_address_by_language(place_id, $sLanguagePrefArraySQL) as langaddress,";
 
1259                                 $sSQL .= "null as placename,";
 
1260                                 $sSQL .= "null as ref,";
 
1261                                 $sSQL .= "avg(ST_X(centroid)) as lon,avg(ST_Y(centroid)) as lat, ";
 
1262                                 //$sSQL .= $sOrderSQL." as porder, ";
 
1263                                 $sSQL .= "-0.15 as importance, ";
 
1264                                 $sSQL .= "(select max(p.importance*(p.rank_address+2)) from place_addressline s, placex p where s.place_id = min(location_property_tiger.place_id) and p.place_id = s.address_place_id and s.isaddress and p.importance is not null) as addressimportance, ";
 
1265                                 $sSQL .= "null as extra_place ";
 
1266                                 $sSQL .= "from location_property_tiger where place_id in ($sPlaceIDs) ";
 
1267                                 $sSQL .= "and 30 between $iMinAddressRank and $iMaxAddressRank ";
 
1268                                 $sSQL .= "group by place_id";
 
1269                                 if (!$bDeDupe) $sSQL .= ",place_id";
 
1271                                 $sSQL .= "select 'L' as osm_type,place_id as osm_id,'place' as class,'house' as type,null as admin_level,30 as rank_search,30 as rank_address,min(place_id) as place_id,'us' as country_code,";
 
1272                                 $sSQL .= "get_address_by_language(place_id, $sLanguagePrefArraySQL) as langaddress,";
 
1273                                 $sSQL .= "null as placename,";
 
1274                                 $sSQL .= "null as ref,";
 
1275                                 $sSQL .= "avg(ST_X(centroid)) as lon,avg(ST_Y(centroid)) as lat, ";
 
1276                                 //$sSQL .= $sOrderSQL." as porder, ";
 
1277                                 $sSQL .= "-0.10 as importance, ";
 
1278                                 $sSQL .= "(select max(p.importance*(p.rank_address+2)) from place_addressline s, placex p where s.place_id = min(location_property_aux.place_id) and p.place_id = s.address_place_id and s.isaddress and p.importance is not null) as addressimportance, ";
 
1279                                 $sSQL .= "null as extra_place ";
 
1280                                 $sSQL .= "from location_property_aux where place_id in ($sPlaceIDs) ";
 
1281                                 $sSQL .= "and 30 between $iMinAddressRank and $iMaxAddressRank ";
 
1282                                 $sSQL .= "group by place_id";
 
1283                                 if (!$bDeDupe) $sSQL .= ",place_id";
 
1284                                 $sSQL .= ",get_address_by_language(place_id, $sLanguagePrefArraySQL) ";
 
1285                                 $sSQL .= "order by importance desc";
 
1286                                 //$sSQL .= "order by rank_search,rank_address,porder asc";
 
1287                                 if (CONST_Debug) { echo "<hr>"; var_dump($sSQL); }
 
1288                                 $aSearchResults = $oDB->getAll($sSQL);
 
1289                                 //var_dump($sSQL,$aSearchResults);exit;
 
1291                                 if (PEAR::IsError($aSearchResults))
 
1293                                         failInternalError("Could not get details for place.", $sSQL, $aSearchResults);
 
1296                 } // end if ($sQuery)
 
1299                         if (isset($_GET['nearlat']) && trim($_GET['nearlat'])!=='' && isset($_GET['nearlon']) && trim($_GET['nearlon']) !== '')
 
1301                                 $iPlaceID = geocodeReverse((float)$_GET['nearlat'], (float)$_GET['nearlon']);
 
1305                                         $aResultPlaceIDs = array($iPlaceID);
 
1306                                         // TODO: this needs refactoring!
 
1308                                         // Get the details for display (is this a redundant extra step?)
 
1309                                         $sPlaceIDs = join(',',$aResultPlaceIDs);
 
1310                                         $sOrderSQL = 'CASE ';
 
1311                                         foreach(array_keys($aResultPlaceIDs) as $iOrder => $iPlaceID)
 
1313                                                 $sOrderSQL .= 'when min(place_id) = '.$iPlaceID.' then '.$iOrder.' ';
 
1315                                         $sOrderSQL .= ' ELSE 10000000 END';
 
1316                                         $sSQL = "select osm_type,osm_id,class,type,admin_level,rank_search,rank_address,min(place_id) as place_id,calculated_country_code as country_code,";
 
1317                                         $sSQL .= "get_address_by_language(place_id, $sLanguagePrefArraySQL) as langaddress,";
 
1318                                         $sSQL .= "get_name_by_language(name, $sLanguagePrefArraySQL) as placename,";
 
1319                                         $sSQL .= "get_name_by_language(name, ARRAY['ref']) as ref,";
 
1320                                         $sSQL .= "avg(ST_X(centroid)) as lon,avg(ST_Y(centroid)) as lat, ";
 
1321                                         //$sSQL .= $sOrderSQL." as porder, ";
 
1322                                         $sSQL .= "coalesce(importance,0.75-(rank_search::float/40)) as importance, ";
 
1323                                         $sSQL .= "(extratags->'place') as extra_place ";
 
1324                                         $sSQL .= "from placex where place_id in ($sPlaceIDs) ";
 
1325                                         $sSQL .= "and (placex.rank_address between $iMinAddressRank and $iMaxAddressRank ";
 
1326                                         if (14 >= $iMinAddressRank && 14 <= $iMaxAddressRank) $sSQL .= " OR (extratags->'place') = 'city'";
 
1327                                         if ($aAddressRankList) $sSQL .= " OR placex.rank_address in (".join(',',$aAddressRankList).")";
 
1329                                         $sSQL .= "group by osm_type,osm_id,class,type,admin_level,rank_search,rank_address,calculated_country_code,importance";
 
1330                                         if (!$bDeDupe) $sSQL .= ",place_id";
 
1331                                         $sSQL .= ",get_address_by_language(place_id, $sLanguagePrefArraySQL) ";
 
1332                                         $sSQL .= ",get_name_by_language(name, $sLanguagePrefArraySQL) ";
 
1333                                         $sSQL .= ",get_name_by_language(name, ARRAY['ref']) ";
 
1334                                         $sSQL .= ",extratags->'place' ";
 
1336                                         $sSQL .= "select 'T' as osm_type,place_id as osm_id,'place' as class,'house' as type,null as admin_level,30 as rank_search,30 as rank_address,min(place_id) as place_id,'us' as country_code,";
 
1337                                         $sSQL .= "get_address_by_language(place_id, $sLanguagePrefArraySQL) as langaddress,";
 
1338                                         $sSQL .= "null as placename,";
 
1339                                         $sSQL .= "null as ref,";
 
1340                                         $sSQL .= "avg(ST_X(centroid)) as lon,avg(ST_Y(centroid)) as lat, ";
 
1341                                         //$sSQL .= $sOrderSQL." as porder, ";
 
1342                                         $sSQL .= "-0.15 as importance, ";
 
1343                                         $sSQL .= "null as extra_place ";
 
1344                                         $sSQL .= "from location_property_tiger where place_id in ($sPlaceIDs) ";
 
1345                                         $sSQL .= "and 30 between $iMinAddressRank and $iMaxAddressRank ";
 
1346                                         $sSQL .= "group by place_id";
 
1347                                         if (!$bDeDupe) $sSQL .= ",place_id";
 
1349                                         $sSQL .= "select 'L' as osm_type,place_id as osm_id,'place' as class,'house' as type,null as admin_level,30 as rank_search,30 as rank_address,min(place_id) as place_id,'us' as country_code,";
 
1350                                         $sSQL .= "get_address_by_language(place_id, $sLanguagePrefArraySQL) as langaddress,";
 
1351                                         $sSQL .= "null as placename,";
 
1352                                         $sSQL .= "null as ref,";
 
1353                                         $sSQL .= "avg(ST_X(centroid)) as lon,avg(ST_Y(centroid)) as lat, ";
 
1354                                         //$sSQL .= $sOrderSQL." as porder, ";
 
1355                                         $sSQL .= "-0.10 as importance, ";
 
1356                                         $sSQL .= "null as extra_place ";
 
1357                                         $sSQL .= "from location_property_aux where place_id in ($sPlaceIDs) ";
 
1358                                         $sSQL .= "and 30 between $iMinAddressRank and $iMaxAddressRank ";
 
1359                                         $sSQL .= "group by place_id";
 
1360                                         if (!$bDeDupe) $sSQL .= ",place_id";
 
1361                                         $sSQL .= ",get_address_by_language(place_id, $sLanguagePrefArraySQL) ";
 
1362                                         $sSQL .= "order by importance desc";
 
1363                                         //$sSQL .= "order by rank_search,rank_address,porder asc";
 
1364                                         if (CONST_Debug) { echo "<hr>", var_dump($sSQL); }
 
1365                                         $aSearchResults = $oDB->getAll($sSQL);
 
1366                                         //var_dump($sSQL,$aSearchResults);exit;
 
1368                                         if (PEAR::IsError($aSearchResults))
 
1370                                                 failInternalError("Could not get details for place (near).", $sSQL, $aSearchResults);
 
1375                                         $aSearchResults = array();
 
1381         $sSearchResult = '';
 
1382         if (!sizeof($aSearchResults) && isset($_GET['q']) && $_GET['q'])
 
1384                 $sSearchResult = 'No Results Found';
 
1386         //var_Dump($aSearchResults);
 
1388         $aClassType = getClassTypesWithImportance();
 
1389         $aRecheckWords = preg_split('/\b/u',$sQuery);
 
1390         foreach($aRecheckWords as $i => $sWord)
 
1392                 if (!$sWord) unset($aRecheckWords[$i]);
 
1394         foreach($aSearchResults as $iResNum => $aResult)
 
1396                 if (CONST_Search_AreaPolygons)
 
1398                         // Get the bounding box and outline polygon
 
1399                         $sSQL = "select place_id,numfeatures,area,outline,";
 
1400                         $sSQL .= "ST_Y(ST_PointN(ST_ExteriorRing(Box2D(outline)),4)) as minlat,ST_Y(ST_PointN(ST_ExteriorRing(Box2D(outline)),2)) as maxlat,";
 
1401                         $sSQL .= "ST_X(ST_PointN(ST_ExteriorRing(Box2D(outline)),1)) as minlon,ST_X(ST_PointN(ST_ExteriorRing(Box2D(outline)),3)) as maxlon,";
 
1402                         $sSQL .= "ST_AsText(outline) as outlinestring from get_place_boundingbox_quick(".$aResult['place_id'].")";
 
1404                         $sSQL = "select place_id,0 as numfeatures,st_area(geometry) as area,";
 
1405                         $sSQL .= "ST_Y(centroid) as centrelat,ST_X(centroid) as centrelon,";
 
1406                         $sSQL .= "ST_Y(ST_PointN(ST_ExteriorRing(Box2D(geometry)),4)) as minlat,ST_Y(ST_PointN(ST_ExteriorRing(Box2D(geometry)),2)) as maxlat,";
 
1407                         $sSQL .= "ST_X(ST_PointN(ST_ExteriorRing(Box2D(geometry)),1)) as minlon,ST_X(ST_PointN(ST_ExteriorRing(Box2D(geometry)),3)) as maxlon";
 
1408                         if ($bAsGeoJSON) $sSQL .= ",ST_AsGeoJSON(geometry) as asgeojson";
 
1409                         if ($bAsKML) $sSQL .= ",ST_AsKML(geometry) as askml";
 
1410                         if ($bAsSVG) $sSQL .= ",ST_AsSVG(geometry) as assvg";
 
1411                         if ($bAsText || $bShowPolygons) $sSQL .= ",ST_AsText(geometry) as astext";
 
1412                         $sSQL .= " from placex where place_id = ".$aResult['place_id'].' and st_geometrytype(Box2D(geometry)) = \'ST_Polygon\'';
 
1413                         $aPointPolygon = $oDB->getRow($sSQL);
 
1414                         if (PEAR::IsError($aPointPolygon))
 
1416                                 failInternalError("Could not get outline.", $sSQL, $aPointPolygon);
 
1418                         if ($aPointPolygon['place_id'])
 
1420                                 if ($bAsGeoJSON) $aResult['asgeojson'] = $aPointPolygon['asgeojson'];
 
1421                                 if ($bAsKML) $aResult['askml'] = $aPointPolygon['askml'];
 
1422                                 if ($bAsSVG) $aResult['assvg'] = $aPointPolygon['assvg'];
 
1423                                 if ($bAsText) $aResult['astext'] = $aPointPolygon['astext'];
 
1425                                 if ($aPointPolygon['centrelon'] !== null && $aPointPolygon['centrelat'] !== null )
 
1427                                         $aResult['lat'] = $aPointPolygon['centrelat'];
 
1428                                         $aResult['lon'] = $aPointPolygon['centrelon'];
 
1432                                         // Translate geometary string to point array
 
1433                                         if (preg_match('#POLYGON\\(\\(([- 0-9.,]+)#',$aPointPolygon['astext'],$aMatch))
 
1435                                                 preg_match_all('/(-?[0-9.]+) (-?[0-9.]+)/',$aMatch[1],$aPolyPoints,PREG_SET_ORDER);
 
1437                                         elseif (preg_match('#MULTIPOLYGON\\(\\(\\(([- 0-9.,]+)#',$aPointPolygon['astext'],$aMatch))
 
1439                                                 preg_match_all('/(-?[0-9.]+) (-?[0-9.]+)/',$aMatch[1],$aPolyPoints,PREG_SET_ORDER);
 
1441                                         elseif (preg_match('#POINT\\((-?[0-9.]+) (-?[0-9.]+)\\)#',$aPointPolygon['astext'],$aMatch))
 
1444                                                 $iSteps = ($fRadius * 40000)^2;
 
1445                                                 $fStepSize = (2*pi())/$iSteps;
 
1446                                                 $aPolyPoints = array();
 
1447                                                 for($f = 0; $f < 2*pi(); $f += $fStepSize)
 
1449                                                         $aPolyPoints[] = array('',$aMatch[1]+($fRadius*sin($f)),$aMatch[2]+($fRadius*cos($f)));
 
1451                                                 $aPointPolygon['minlat'] = $aPointPolygon['minlat'] - $fRadius;
 
1452                                                 $aPointPolygon['maxlat'] = $aPointPolygon['maxlat'] + $fRadius;
 
1453                                                 $aPointPolygon['minlon'] = $aPointPolygon['minlon'] - $fRadius;
 
1454                                                 $aPointPolygon['maxlon'] = $aPointPolygon['maxlon'] + $fRadius;
 
1458                                 // Output data suitable for display (points and a bounding box)
 
1459                                 if ($bShowPolygons && isset($aPolyPoints))
 
1461                                         $aResult['aPolyPoints'] = array();
 
1462                                         foreach($aPolyPoints as $aPoint)
 
1464                                                 $aResult['aPolyPoints'][] = array($aPoint[1], $aPoint[2]);
 
1467                                 $aResult['aBoundingBox'] = array($aPointPolygon['minlat'],$aPointPolygon['maxlat'],$aPointPolygon['minlon'],$aPointPolygon['maxlon']);
 
1471                 if ($aResult['extra_place'] == 'city')
 
1473                         $aResult['class'] = 'place';
 
1474                         $aResult['type'] = 'city';
 
1475                         $aResult['rank_search'] = 16;
 
1478                 if (!isset($aResult['aBoundingBox']))
 
1481                         $fDiameter = 0.0001;
 
1483                         if (isset($aClassType[$aResult['class'].':'.$aResult['type'].':'.$aResult['admin_level']]['defdiameter'])
 
1484                                         && $aClassType[$aResult['class'].':'.$aResult['type'].':'.$aResult['admin_level']]['defdiameter'])
 
1486                                 $fDiameter = $aClassType[$aResult['class'].':'.$aResult['type'].':'.$aResult['admin_level']]['defzoom'];
 
1488                         elseif (isset($aClassType[$aResult['class'].':'.$aResult['type']]['defdiameter'])
 
1489                                         && $aClassType[$aResult['class'].':'.$aResult['type']]['defdiameter'])
 
1491                                 $fDiameter = $aClassType[$aResult['class'].':'.$aResult['type']]['defdiameter'];
 
1493                         $fRadius = $fDiameter / 2;
 
1495                         $iSteps = max(8,min(100,$fRadius * 3.14 * 100000));
 
1496                         $fStepSize = (2*pi())/$iSteps;
 
1497                         $aPolyPoints = array();
 
1498                         for($f = 0; $f < 2*pi(); $f += $fStepSize)
 
1500                                 $aPolyPoints[] = array('',$aResult['lon']+($fRadius*sin($f)),$aResult['lat']+($fRadius*cos($f)));
 
1502                         $aPointPolygon['minlat'] = $aResult['lat'] - $fRadius;
 
1503                         $aPointPolygon['maxlat'] = $aResult['lat'] + $fRadius;
 
1504                         $aPointPolygon['minlon'] = $aResult['lon'] - $fRadius;
 
1505                         $aPointPolygon['maxlon'] = $aResult['lon'] + $fRadius;
 
1507                         // Output data suitable for display (points and a bounding box)
 
1510                                 $aResult['aPolyPoints'] = array();
 
1511                                 foreach($aPolyPoints as $aPoint)
 
1513                                         $aResult['aPolyPoints'][] = array($aPoint[1], $aPoint[2]);
 
1516                         $aResult['aBoundingBox'] = array($aPointPolygon['minlat'],$aPointPolygon['maxlat'],$aPointPolygon['minlon'],$aPointPolygon['maxlon']);
 
1519                 // Is there an icon set for this type of result?
 
1520                 if (isset($aClassType[$aResult['class'].':'.$aResult['type']]['icon'])
 
1521                                 && $aClassType[$aResult['class'].':'.$aResult['type']]['icon'])
 
1523                         $aResult['icon'] = CONST_Website_BaseURL.'images/mapicons/'.$aClassType[$aResult['class'].':'.$aResult['type']]['icon'].'.p.20.png';
 
1526                 if (isset($aClassType[$aResult['class'].':'.$aResult['type']]['label'])
 
1527                                 && $aClassType[$aResult['class'].':'.$aResult['type']]['label'])
 
1529                         $aResult['label'] = $aClassType[$aResult['class'].':'.$aResult['type']]['label'];
 
1532                 if ($bShowAddressDetails)
 
1534                         $aResult['address'] = getAddressDetails($oDB, $sLanguagePrefArraySQL, $aResult['place_id'], $aResult['country_code']);
 
1535                         if ($aResult['extra_place'] == 'city' && !isset($aResult['address']['city']))
 
1537                                 $aResult['address'] = array_merge(array('city' => array_shift(array_values($aResult['address']))), $aResult['address']);
 
1540                         //var_dump($aResult['address']);
 
1544                 // Adjust importance for the number of exact string matches in the result
 
1545                 $aResult['importance'] = max(0.001,$aResult['importance']);
 
1547                 $sAddress = $aResult['langaddress'];
 
1548                 foreach($aRecheckWords as $i => $sWord)
 
1550                         if (stripos($sAddress, $sWord)!==false) $iCountWords++;
 
1553                 $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
 
1555                 //if (CONST_Debug) var_dump($aResult['class'].':'.$aResult['type'].':'.$aResult['admin_level']);
 
1557                    if (isset($aClassType[$aResult['class'].':'.$aResult['type'].':'.$aResult['admin_level']]['importance'])
 
1558                    && $aClassType[$aResult['class'].':'.$aResult['type'].':'.$aResult['admin_level']]['importance'])
 
1560                    $aResult['importance'] = $aClassType[$aResult['class'].':'.$aResult['type'].':'.$aResult['admin_level']]['importance'];
 
1562                    elseif (isset($aClassType[$aResult['class'].':'.$aResult['type']]['importance'])
 
1563                    && $aClassType[$aResult['class'].':'.$aResult['type']]['importance'])
 
1565                    $aResult['importance'] = $aClassType[$aResult['class'].':'.$aResult['type']]['importance'];
 
1569                    $aResult['importance'] = 1000000000000000;
 
1572                 $aResult['name'] = $aResult['langaddress'];
 
1573                 $aResult['foundorder'] = -$aResult['addressimportance'];
 
1574                 $aSearchResults[$iResNum] = $aResult;
 
1576         uasort($aSearchResults, 'byImportance');
 
1578         $aOSMIDDone = array();
 
1579         $aClassTypeNameDone = array();
 
1580         $aToFilter = $aSearchResults;
 
1581         $aSearchResults = array();
 
1584         foreach($aToFilter as $iResNum => $aResult)
 
1586                 if ($aResult['type'] == 'adminitrative') $aResult['type'] = 'administrative';
 
1587                 $aExcludePlaceIDs[$aResult['place_id']] = $aResult['place_id'];
 
1590                         $fLat = $aResult['lat'];
 
1591                         $fLon = $aResult['lon'];
 
1592                         if (isset($aResult['zoom'])) $iZoom = $aResult['zoom'];
 
1595                 if (!$bDeDupe || (!isset($aOSMIDDone[$aResult['osm_type'].$aResult['osm_id']])
 
1596                                         && !isset($aClassTypeNameDone[$aResult['osm_type'].$aResult['class'].$aResult['type'].$aResult['name'].$aResult['admin_level']])))
 
1598                         $aOSMIDDone[$aResult['osm_type'].$aResult['osm_id']] = true;
 
1599                         $aClassTypeNameDone[$aResult['osm_type'].$aResult['class'].$aResult['type'].$aResult['name'].$aResult['admin_level']] = true;
 
1600                         $aSearchResults[] = $aResult;
 
1603                 // Absolute limit on number of results
 
1604                 if (sizeof($aSearchResults) >= $iFinalLimit) break;
 
1607         $sDataDate = $oDB->getOne("select TO_CHAR(lastimportdate - '2 minutes'::interval,'YYYY/MM/DD HH24:MI')||' GMT' from import_status limit 1");
 
1609         if (isset($_GET['nearlat']) && isset($_GET['nearlon']))
 
1611                 $sQuery .= ' ['.$_GET['nearlat'].','.$_GET['nearlon'].']';
 
1616                 logEnd($oDB, $hLog, sizeof($aToFilter));
 
1618         $sMoreURL = CONST_Website_BaseURL.'search?format='.urlencode($sOutputFormat).'&exclude_place_ids='.join(',',$aExcludePlaceIDs);
 
1619         if (isset($_SERVER["HTTP_ACCEPT_LANGUAGE"])) $sMoreURL .= '&accept-language='.$_SERVER["HTTP_ACCEPT_LANGUAGE"];
 
1620         if ($bShowPolygons) $sMoreURL .= '&polygon=1';
 
1621         if ($bShowAddressDetails) $sMoreURL .= '&addressdetails=1';
 
1622         if (isset($_GET['viewbox']) && $_GET['viewbox']) $sMoreURL .= '&viewbox='.urlencode($_GET['viewbox']);
 
1623         if (isset($_GET['nearlat']) && isset($_GET['nearlon'])) $sMoreURL .= '&nearlat='.(float)$_GET['nearlat'].'&nearlon='.(float)$_GET['nearlon'];
 
1624         $sMoreURL .= '&q='.urlencode($sQuery);
 
1626         if (CONST_Debug) exit;
 
1628         include(CONST_BasePath.'/lib/template/search-'.$sOutputFormat.'.php');