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 $bReverseInPlan = true;
80 if (isset($aLangPrefOrder['name:de'])) $bReverseInPlan = true;
81 if (isset($aLangPrefOrder['name:ru'])) $bReverseInPlan = true;
82 if (isset($aLangPrefOrder['name:ja'])) $bReverseInPlan = true;
83 if (isset($aLangPrefOrder['name:pl'])) $bReverseInPlan = true;
86 $sLanguagePrefArraySQL = "ARRAY[".join(',',array_map("getDBQuoted",$aLangPrefOrder))."]";
88 if (isset($_GET['exclude_place_ids']) && $_GET['exclude_place_ids'])
90 foreach(explode(',',$_GET['exclude_place_ids']) as $iExcludedPlaceID)
92 $iExcludedPlaceID = (int)$iExcludedPlaceID;
93 if ($iExcludedPlaceID) $aExcludePlaceIDs[$iExcludedPlaceID] = $iExcludedPlaceID;
97 // Only certain ranks of feature
98 if (isset($_GET['featureType']) && !isset($_GET['featuretype'])) $_GET['featuretype'] = $_GET['featureType'];
100 if (isset($_GET['featuretype']))
102 switch($_GET['featuretype'])
105 $iMinAddressRank = $iMaxAddressRank = 4;
108 $iMinAddressRank = $iMaxAddressRank = 8;
111 $iMinAddressRank = 14;
112 $iMaxAddressRank = 16;
115 $iMinAddressRank = 8;
116 $iMaxAddressRank = 20;
121 if (isset($_GET['countrycodes']))
123 $aCountryCodes = array();
124 foreach(explode(',',$_GET['countrycodes']) as $sCountryCode)
126 if (preg_match('/^[a-zA-Z][a-zA-Z]$/', $sCountryCode))
128 $aCountryCodes[] = "'".strtolower($sCountryCode)."'";
131 $sCountryCodesSQL = join(',', $aCountryCodes);
135 $sQuery = (isset($_GET['q'])?trim($_GET['q']):'');
136 if (!$sQuery && isset($_SERVER['PATH_INFO']) && $_SERVER['PATH_INFO'][0] == '/')
138 $sQuery = substr($_SERVER['PATH_INFO'], 1);
140 // reverse order of '/' separated string
141 $aPhrases = explode('/', $sQuery);
142 $aPhrases = array_reverse($aPhrases);
143 $sQuery = join(', ',$aPhrases);
147 $aStructuredOptions = array(
148 array('amenity', 26, 30, false),
149 array('street', 26, 30, false),
150 array('city', 14, 24, false),
151 array('county', 9, 13, false),
152 array('state', 8, 8, false),
153 array('country', 4, 4, false),
154 array('postalcode', 5, 11, array(5, 11)),
156 $aStructuredQuery = array();
157 $sAllowedTypesSQLList = '';
158 foreach($aStructuredOptions as $aStructuredOption)
160 loadStructuredAddressElement($aStructuredQuery, $iMinAddressRank, $iMaxAddressRank, $aAddressRankList, $_GET, $aStructuredOption[0], $aStructuredOption[1], $aStructuredOption[2], $aStructuredOption[3]);
162 if (sizeof($aStructuredQuery) > 0)
164 $sQuery = join(', ', $aStructuredQuery);
165 if ($iMaxAddressRank < 30)
167 $sAllowedTypesSQLList = '(\'place\',\'boundary\')';
173 $hLog = logStart($oDB, 'search', $sQuery, $aLangPrefOrder);
175 // Hack to make it handle "new york, ny" (and variants) correctly
176 $sQuery = str_ireplace(array('New York, ny','new york, new york', 'New York ny','new york new york'), 'new york city, ny', $sQuery);
177 if (isset($aLangPrefOrder['name:en']))
179 $sQuery = preg_replace('/,\s*il\s*(,|$)/',', illinois\1', $sQuery);
180 $sQuery = preg_replace('/,\s*al\s*(,|$)/',', alabama\1', $sQuery);
181 $sQuery = preg_replace('/,\s*la\s*(,|$)/',', louisiana\1', $sQuery);
184 // If we have a view box create the SQL
185 // Small is the actual view box, Large is double (on each axis) that
186 $sViewboxCentreSQL = $sViewboxSmallSQL = $sViewboxLargeSQL = false;
187 if (isset($_GET['viewboxlbrt']) && $_GET['viewboxlbrt'])
189 $aCoOrdinatesLBRT = explode(',',$_GET['viewboxlbrt']);
190 $_GET['viewbox'] = $aCoOrdinatesLBRT[0].','.$aCoOrdinatesLBRT[3].','.$aCoOrdinatesLBRT[2].','.$aCoOrdinatesLBRT[1];
192 if (isset($_GET['viewbox']) && $_GET['viewbox'])
194 $aCoOrdinates = explode(',',$_GET['viewbox']);
195 $sViewboxSmallSQL = "ST_SetSRID(ST_MakeBox2D(ST_Point(".(float)$aCoOrdinates[0].",".(float)$aCoOrdinates[1]."),ST_Point(".(float)$aCoOrdinates[2].",".(float)$aCoOrdinates[3].")),4326)";
196 $fHeight = $aCoOrdinates[0]-$aCoOrdinates[2];
197 $fWidth = $aCoOrdinates[1]-$aCoOrdinates[3];
198 $aCoOrdinates[0] += $fHeight;
199 $aCoOrdinates[2] -= $fHeight;
200 $aCoOrdinates[1] += $fWidth;
201 $aCoOrdinates[3] -= $fWidth;
202 $sViewboxLargeSQL = "ST_SetSRID(ST_MakeBox2D(ST_Point(".(float)$aCoOrdinates[0].",".(float)$aCoOrdinates[1]."),ST_Point(".(float)$aCoOrdinates[2].",".(float)$aCoOrdinates[3].")),4326)";
206 $bBoundingBoxSearch = false;
208 if (isset($_GET['route']) && $_GET['route'] && isset($_GET['routewidth']) && $_GET['routewidth'])
210 $aPoints = explode(',',$_GET['route']);
211 if (sizeof($aPoints) % 2 != 0)
213 userError("Uneven number of points");
216 $sViewboxCentreSQL = "ST_SetSRID('LINESTRING(";
218 foreach($aPoints as $i => $fPoint)
222 if ($i != 1) $sViewboxCentreSQL .= ",";
223 $sViewboxCentreSQL .= ((float)$fPoint).' '.$fPrevCoord;
227 $fPrevCoord = (float)$fPoint;
230 $sViewboxCentreSQL .= ")'::geometry,4326)";
232 $sSQL = "select st_buffer(".$sViewboxCentreSQL.",".(float)($_GET['routewidth']/69).")";
233 $sViewboxSmallSQL = $oDB->getOne($sSQL);
234 if (PEAR::isError($sViewboxSmallSQL))
236 failInternalError("Could not get small viewbox.", $sSQL, $sViewboxSmallSQL);
238 $sViewboxSmallSQL = "'".$sViewboxSmallSQL."'::geometry";
240 $sSQL = "select st_buffer(".$sViewboxCentreSQL.",".(float)($_GET['routewidth']/30).")";
241 $sViewboxLargeSQL = $oDB->getOne($sSQL);
242 if (PEAR::isError($sViewboxLargeSQL))
244 failInternalError("Could not get large viewbox.", $sSQL, $sViewboxLargeSQL);
246 $sViewboxLargeSQL = "'".$sViewboxLargeSQL."'::geometry";
247 $bBoundingBoxSearch = true;
250 // Do we have anything that looks like a lat/lon pair?
251 if (preg_match('/\\b([NS])[ ]+([0-9]+[0-9.]*)[ ]+([0-9.]+)?[, ]+([EW])[ ]+([0-9]+)[ ]+([0-9]+[0-9.]*)?\\b/', $sQuery, $aData))
253 $fQueryLat = ($aData[1]=='N'?1:-1) * ($aData[2] + $aData[3]/60);
254 $fQueryLon = ($aData[4]=='E'?1:-1) * ($aData[5] + $aData[6]/60);
255 if ($fQueryLat <= 90.1 && $fQueryLat >= -90.1 && $fQueryLon <= 180.1 && $fQueryLon >= -180.1)
257 $_GET['nearlat'] = $fQueryLat;
258 $_GET['nearlon'] = $fQueryLon;
259 $sQuery = trim(str_replace($aData[0], ' ', $sQuery));
262 elseif (preg_match('/\\b([0-9]+)[ ]+([0-9]+[0-9.]*)?[ ]+([NS])[, ]+([0-9]+)[ ]+([0-9]+[0-9.]*)?[ ]+([EW])\\b/', $sQuery, $aData))
264 $fQueryLat = ($aData[3]=='N'?1:-1) * ($aData[1] + $aData[2]/60);
265 $fQueryLon = ($aData[6]=='E'?1:-1) * ($aData[4] + $aData[5]/60);
266 if ($fQueryLat <= 90.1 && $fQueryLat >= -90.1 && $fQueryLon <= 180.1 && $fQueryLon >= -180.1)
268 $_GET['nearlat'] = $fQueryLat;
269 $_GET['nearlon'] = $fQueryLon;
270 $sQuery = trim(str_replace($aData[0], ' ', $sQuery));
273 elseif (preg_match('/(\\[|^|\\b)(-?[0-9]+[0-9]*\\.[0-9]+)[, ]+(-?[0-9]+[0-9]*\\.[0-9]+)(\\]|$|\\b)/', $sQuery, $aData))
275 $fQueryLat = $aData[2];
276 $fQueryLon = $aData[3];
277 if ($fQueryLat <= 90.1 && $fQueryLat >= -90.1 && $fQueryLon <= 180.1 && $fQueryLon >= -180.1)
279 $_GET['nearlat'] = $fQueryLat;
280 $_GET['nearlon'] = $fQueryLon;
281 $sQuery = trim(str_replace($aData[0], ' ', $sQuery));
285 if ($sQuery || $aStructuredQuery)
287 // Start with a blank search
289 array('iSearchRank' => 0, 'iNamePhrase' => -1, 'sCountryCode' => false, 'aName'=>array(), 'aAddress'=>array(), 'aFullNameAddress'=>array(),
290 'aNameNonSearch'=>array(), 'aAddressNonSearch'=>array(),
291 'sOperator'=>'', 'aFeatureName' => array(), 'sClass'=>'', 'sType'=>'', 'sHouseNumber'=>'', 'fLat'=>'', 'fLon'=>'', 'fRadius'=>'')
294 $sNearPointSQL = false;
295 if (isset($_GET['nearlat']) && isset($_GET['nearlon']))
297 $sNearPointSQL = "ST_SetSRID(ST_Point(".(float)$_GET['nearlon'].",".(float)$_GET['nearlat']."),4326)";
298 $aSearches[0]['fLat'] = (float)$_GET['nearlat'];
299 $aSearches[0]['fLon'] = (float)$_GET['nearlon'];
300 $aSearches[0]['fRadius'] = 0.1;
303 $bSpecialTerms = false;
304 preg_match_all('/\\[(.*)=(.*)\\]/', $sQuery, $aSpecialTermsRaw, PREG_SET_ORDER);
305 $aSpecialTerms = array();
306 foreach($aSpecialTermsRaw as $aSpecialTerm)
308 $sQuery = str_replace($aSpecialTerm[0], ' ', $sQuery);
309 $aSpecialTerms[strtolower($aSpecialTerm[1])] = $aSpecialTerm[2];
312 preg_match_all('/\\[([\\w ]*)\\]/u', $sQuery, $aSpecialTermsRaw, PREG_SET_ORDER);
313 $aSpecialTerms = array();
314 if (isset($aStructuredQuery['amenity']) && $aStructuredQuery['amenity'])
316 $aSpecialTermsRaw[] = array('['.$aStructuredQuery['amenity'].']', $aStructuredQuery['amenity']);
317 unset($aStructuredQuery['amenity']);
319 foreach($aSpecialTermsRaw as $aSpecialTerm)
321 $sQuery = str_replace($aSpecialTerm[0], ' ', $sQuery);
322 $sToken = $oDB->getOne("select make_standard_name('".$aSpecialTerm[1]."') as string");
323 $sSQL = 'select * from (select word_id,word_token, word, class, type, country_code, operator';
324 $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';
325 if (CONST_Debug) var_Dump($sSQL);
326 $aSearchWords = $oDB->getAll($sSQL);
327 $aNewSearches = array();
328 foreach($aSearches as $aSearch)
330 foreach($aSearchWords as $aSearchTerm)
332 $aNewSearch = $aSearch;
333 if ($aSearchTerm['country_code'])
335 $aNewSearch['sCountryCode'] = strtolower($aSearchTerm['country_code']);
336 $aNewSearches[] = $aNewSearch;
337 $bSpecialTerms = true;
339 if ($aSearchTerm['class'])
341 $aNewSearch['sClass'] = $aSearchTerm['class'];
342 $aNewSearch['sType'] = $aSearchTerm['type'];
343 $aNewSearches[] = $aNewSearch;
344 $bSpecialTerms = true;
348 $aSearches = $aNewSearches;
351 // Split query into phrases
352 // Commas are used to reduce the search space by indicating where phrases split
353 if (sizeof($aStructuredQuery) > 0)
355 $aPhrases = $aStructuredQuery;
356 $bStructuredPhrases = true;
360 $aPhrases = explode(',',$sQuery);
361 $bStructuredPhrases = false;
364 // Convert each phrase to standard form
365 // Create a list of standard words
366 // Get all 'sets' of words
367 // Generate a complete list of all
369 foreach($aPhrases as $iPhrase => $sPhrase)
371 $aPhrase = $oDB->getRow("select make_standard_name('".pg_escape_string($sPhrase)."') as string");
372 if (PEAR::isError($aPhrase))
374 userError("Illegal query string (not an UTF-8 string): ".$sPhrase);
375 if (CONST_Debug) var_dump($aPhrase);
378 if (trim($aPhrase['string']))
380 $aPhrases[$iPhrase] = $aPhrase;
381 $aPhrases[$iPhrase]['words'] = explode(' ',$aPhrases[$iPhrase]['string']);
382 $aPhrases[$iPhrase]['wordsets'] = getWordSets($aPhrases[$iPhrase]['words'], 0);
383 $aTokens = array_merge($aTokens, getTokensFromSets($aPhrases[$iPhrase]['wordsets']));
387 unset($aPhrases[$iPhrase]);
391 // reindex phrases - we make assumptions later on
392 $aPhraseTypes = array_keys($aPhrases);
393 $aPhrases = array_values($aPhrases);
395 if (sizeof($aTokens))
398 // Check which tokens we have, get the ID numbers
399 $sSQL = 'select word_id,word_token, word, class, type, country_code, operator, search_name_count';
400 $sSQL .= ' from word where word_token in ('.join(',',array_map("getDBQuoted",$aTokens)).')';
401 //$sSQL .= ' and search_name_count < '.CONST_Max_Word_Frequency;
402 //$sSQL .= ' group by word_token, word, class, type, country_code';
404 if (CONST_Debug) var_Dump($sSQL);
406 $aValidTokens = array();
407 if (sizeof($aTokens)) $aDatabaseWords = $oDB->getAll($sSQL);
408 else $aDatabaseWords = array();
409 if (PEAR::IsError($aDatabaseWords))
411 failInternalError("Could not get word tokens.", $sSQL, $aDatabaseWords);
413 $aPossibleMainWordIDs = array();
414 $aWordFrequencyScores = array();
415 foreach($aDatabaseWords as $aToken)
417 // Very special case - require 2 letter country param to match the country code found
418 if ($bStructuredPhrases && $aToken['country_code'] && !empty($aStructuredQuery['country'])
419 && strlen($aStructuredQuery['country']) == 2 && strtolower($aStructuredQuery['country']) != $aToken['country_code'])
424 if (isset($aValidTokens[$aToken['word_token']]))
426 $aValidTokens[$aToken['word_token']][] = $aToken;
430 $aValidTokens[$aToken['word_token']] = array($aToken);
432 if (!$aToken['class'] && !$aToken['country_code']) $aPossibleMainWordIDs[$aToken['word_id']] = 1;
433 $aWordFrequencyScores[$aToken['word_id']] = $aToken['search_name_count'] + 1;
435 if (CONST_Debug) var_Dump($aPhrases, $aValidTokens);
437 // Try and calculate GB postcodes we might be missing
438 foreach($aTokens as $sToken)
440 // Source of gb postcodes is now definitive - always use
441 if (preg_match('/^([A-Z][A-Z]?[0-9][0-9A-Z]? ?[0-9])([A-Z][A-Z])$/', strtoupper(trim($sToken)), $aData))
443 if (substr($aData[1],-2,1) != ' ')
445 $aData[0] = substr($aData[0],0,strlen($aData[1]-1)).' '.substr($aData[0],strlen($aData[1]-1));
446 $aData[1] = substr($aData[1],0,-1).' '.substr($aData[1],-1,1);
448 $aGBPostcodeLocation = gbPostcodeCalculate($aData[0], $aData[1], $aData[2], $oDB);
449 if ($aGBPostcodeLocation)
451 $aValidTokens[$sToken] = $aGBPostcodeLocation;
454 // US ZIP+4 codes - if there is no token,
455 // merge in the 5-digit ZIP code
456 else if (!isset($aValidTokens[$sToken]) && preg_match('/^([0-9]{5}) [0-9]{4}$/', $sToken, $aData))
458 if (isset($aValidTokens[$aData[1]]))
460 foreach($aValidTokens[$aData[1]] as $aToken)
462 if (!$aToken['class'])
464 if (isset($aValidTokens[$sToken]))
466 $aValidTokens[$sToken][] = $aToken;
470 $aValidTokens[$sToken] = array($aToken);
478 foreach($aTokens as $sToken)
480 // Unknown single word token with a number - assume it is a house number
481 if (!isset($aValidTokens[' '.$sToken]) && strpos($sToken,' ') === false && preg_match('/[0-9]/', $sToken))
483 $aValidTokens[' '.$sToken] = array(array('class'=>'place','type'=>'house'));
487 // Any words that have failed completely?
490 // Start the search process
491 $aResultPlaceIDs = array();
494 Calculate all searches using aValidTokens i.e.
495 'Wodsworth Road, Sheffield' =>
499 0 1 (wodsworth)(road)
502 Score how good the search is so they can be ordered
504 foreach($aPhrases as $iPhrase => $sPhrase)
506 $aNewPhraseSearches = array();
507 if ($bStructuredPhrases) $sPhraseType = $aPhraseTypes[$iPhrase];
508 else $sPhraseType = '';
510 foreach($aPhrases[$iPhrase]['wordsets'] as $aWordset)
512 $aWordsetSearches = $aSearches;
514 // Add all words from this wordset
515 foreach($aWordset as $iToken => $sToken)
517 //echo "<br><b>$sToken</b>";
518 $aNewWordsetSearches = array();
520 foreach($aWordsetSearches as $aCurrentSearch)
523 //var_dump($aCurrentSearch);
526 // If the token is valid
527 if (isset($aValidTokens[' '.$sToken]))
529 foreach($aValidTokens[' '.$sToken] as $aSearchTerm)
531 $aSearch = $aCurrentSearch;
532 $aSearch['iSearchRank']++;
533 if (($sPhraseType == '' || $sPhraseType == 'country') && !empty($aSearchTerm['country_code']) && $aSearchTerm['country_code'] != '0')
535 if ($aSearch['sCountryCode'] === false)
537 $aSearch['sCountryCode'] = strtolower($aSearchTerm['country_code']);
538 // Country is almost always at the end of the string - increase score for finding it anywhere else (optimisation)
539 // If reverse order is enabled, it may appear at the beginning as well.
540 if (($iToken+1 != sizeof($aWordset) || $iPhrase+1 != sizeof($aPhrases)) &&
541 (!$bReverseInPlan || $iToken > 0 || $iPhrase > 0))
543 $aSearch['iSearchRank'] += 5;
545 if ($aSearch['iSearchRank'] < $iMaxRank) $aNewWordsetSearches[] = $aSearch;
548 elseif (isset($aSearchTerm['lat']) && $aSearchTerm['lat'] !== '' && $aSearchTerm['lat'] !== null)
550 if ($aSearch['fLat'] === '')
552 $aSearch['fLat'] = $aSearchTerm['lat'];
553 $aSearch['fLon'] = $aSearchTerm['lon'];
554 $aSearch['fRadius'] = $aSearchTerm['radius'];
555 if ($aSearch['iSearchRank'] < $iMaxRank) $aNewWordsetSearches[] = $aSearch;
558 elseif ($sPhraseType == 'postalcode')
560 // 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
561 if (isset($aSearchTerm['word_id']) && $aSearchTerm['word_id'])
563 // If we already have a name try putting the postcode first
564 if (sizeof($aSearch['aName']))
566 $aNewSearch = $aSearch;
567 $aNewSearch['aAddress'] = array_merge($aNewSearch['aAddress'], $aNewSearch['aName']);
568 $aNewSearch['aName'] = array();
569 $aNewSearch['aName'][$aSearchTerm['word_id']] = $aSearchTerm['word_id'];
570 if ($aSearch['iSearchRank'] < $iMaxRank) $aNewWordsetSearches[] = $aNewSearch;
573 if (sizeof($aSearch['aName']))
575 if ((!$bStructuredPhrases || $iPhrase > 0) && $sPhraseType != 'country' && (!isset($aValidTokens[$sToken]) || strlen($sToken) < 4 || strpos($sToken, ' ') !== false))
577 $aSearch['aAddress'][$aSearchTerm['word_id']] = $aSearchTerm['word_id'];
581 $aCurrentSearch['aFullNameAddress'][$aSearchTerm['word_id']] = $aSearchTerm['word_id'];
582 $aSearch['iSearchRank'] += 1000; // skip;
587 $aSearch['aName'][$aSearchTerm['word_id']] = $aSearchTerm['word_id'];
588 //$aSearch['iNamePhrase'] = $iPhrase;
590 if ($aSearch['iSearchRank'] < $iMaxRank) $aNewWordsetSearches[] = $aSearch;
594 elseif (($sPhraseType == '' || $sPhraseType == 'street') && $aSearchTerm['class'] == 'place' && $aSearchTerm['type'] == 'house')
596 if ($aSearch['sHouseNumber'] === '')
598 $aSearch['sHouseNumber'] = $sToken;
599 if ($aSearch['iSearchRank'] < $iMaxRank) $aNewWordsetSearches[] = $aSearch;
601 // Fall back to not searching for this item (better than nothing)
602 $aSearch = $aCurrentSearch;
603 $aSearch['iSearchRank'] += 1;
604 if ($aSearch['iSearchRank'] < $iMaxRank) $aNewWordsetSearches[] = $aSearch;
608 elseif ($sPhraseType == '' && $aSearchTerm['class'] !== '' && $aSearchTerm['class'] !== null)
610 if ($aSearch['sClass'] === '')
612 $aSearch['sOperator'] = $aSearchTerm['operator'];
613 $aSearch['sClass'] = $aSearchTerm['class'];
614 $aSearch['sType'] = $aSearchTerm['type'];
615 if (sizeof($aSearch['aName'])) $aSearch['sOperator'] = 'name';
616 else $aSearch['sOperator'] = 'near'; // near = in for the moment
617 if (strlen($aSearchTerm['operator']) == 0) $aSearch['iSearchRank'] += 1;
619 // Do we have a shortcut id?
620 if ($aSearch['sOperator'] == 'name')
622 $sSQL = "select get_tagpair('".$aSearch['sClass']."', '".$aSearch['sType']."')";
623 if ($iAmenityID = $oDB->getOne($sSQL))
625 $aValidTokens[$aSearch['sClass'].':'.$aSearch['sType']] = array('word_id' => $iAmenityID);
626 $aSearch['aName'][$iAmenityID] = $iAmenityID;
627 $aSearch['sClass'] = '';
628 $aSearch['sType'] = '';
631 if ($aSearch['iSearchRank'] < $iMaxRank) $aNewWordsetSearches[] = $aSearch;
634 elseif (isset($aSearchTerm['word_id']) && $aSearchTerm['word_id'])
636 if (sizeof($aSearch['aName']))
638 if ((!$bStructuredPhrases || $iPhrase > 0) && $sPhraseType != 'country' && (!isset($aValidTokens[$sToken]) || strlen($sToken) < 4 || strpos($sToken, ' ') !== false))
640 $aSearch['aAddress'][$aSearchTerm['word_id']] = $aSearchTerm['word_id'];
644 $aCurrentSearch['aFullNameAddress'][$aSearchTerm['word_id']] = $aSearchTerm['word_id'];
645 $aSearch['iSearchRank'] += 1000; // skip;
650 $aSearch['aName'][$aSearchTerm['word_id']] = $aSearchTerm['word_id'];
651 //$aSearch['iNamePhrase'] = $iPhrase;
653 if ($aSearch['iSearchRank'] < $iMaxRank) $aNewWordsetSearches[] = $aSearch;
657 if (isset($aValidTokens[$sToken]))
659 // Allow searching for a word - but at extra cost
660 foreach($aValidTokens[$sToken] as $aSearchTerm)
662 if (isset($aSearchTerm['word_id']) && $aSearchTerm['word_id'])
664 if ((!$bStructuredPhrases || $iPhrase > 0) && sizeof($aCurrentSearch['aName']) && strlen($sToken) >= 4)
666 $aSearch = $aCurrentSearch;
667 $aSearch['iSearchRank'] += 1;
668 if ($aWordFrequencyScores[$aSearchTerm['word_id']] < CONST_Max_Word_Frequency)
670 $aSearch['aAddress'][$aSearchTerm['word_id']] = $aSearchTerm['word_id'];
671 if ($aSearch['iSearchRank'] < $iMaxRank) $aNewWordsetSearches[] = $aSearch;
673 elseif (isset($aValidTokens[' '.$sToken])) // revert to the token version?
675 foreach($aValidTokens[' '.$sToken] as $aSearchTermToken)
677 if (empty($aSearchTermToken['country_code'])
678 && empty($aSearchTermToken['lat'])
679 && empty($aSearchTermToken['class']))
681 $aSearch = $aCurrentSearch;
682 $aSearch['iSearchRank'] += 1;
683 $aSearch['aAddress'][$aSearchTermToken['word_id']] = $aSearchTermToken['word_id'];
684 if ($aSearch['iSearchRank'] < $iMaxRank) $aNewWordsetSearches[] = $aSearch;
690 $aSearch['aAddressNonSearch'][$aSearchTerm['word_id']] = $aSearchTerm['word_id'];
691 if ($aSearch['iSearchRank'] < $iMaxRank) $aNewWordsetSearches[] = $aSearch;
695 if (!sizeof($aCurrentSearch['aName']) || $aCurrentSearch['iNamePhrase'] == $iPhrase)
697 $aSearch = $aCurrentSearch;
698 $aSearch['iSearchRank'] += 2;
699 if (preg_match('#^[0-9]+$#', $sToken)) $aSearch['iSearchRank'] += 2;
700 if ($aWordFrequencyScores[$aSearchTerm['word_id']] < CONST_Max_Word_Frequency)
701 $aSearch['aName'][$aSearchTerm['word_id']] = $aSearchTerm['word_id'];
703 $aSearch['aNameNonSearch'][$aSearchTerm['word_id']] = $aSearchTerm['word_id'];
704 $aSearch['iNamePhrase'] = $iPhrase;
705 if ($aSearch['iSearchRank'] < $iMaxRank) $aNewWordsetSearches[] = $aSearch;
712 // Allow skipping a word - but at EXTREAM cost
713 //$aSearch = $aCurrentSearch;
714 //$aSearch['iSearchRank']+=100;
715 //$aNewWordsetSearches[] = $aSearch;
719 usort($aNewWordsetSearches, 'bySearchRank');
720 $aWordsetSearches = array_slice($aNewWordsetSearches, 0, 50);
722 //var_Dump('<hr>',sizeof($aWordsetSearches)); exit;
724 $aNewPhraseSearches = array_merge($aNewPhraseSearches, $aNewWordsetSearches);
725 usort($aNewPhraseSearches, 'bySearchRank');
727 $aSearchHash = array();
728 foreach($aNewPhraseSearches as $iSearch => $aSearch)
730 $sHash = serialize($aSearch);
731 if (isset($aSearchHash[$sHash])) unset($aNewPhraseSearches[$iSearch]);
732 else $aSearchHash[$sHash] = 1;
735 $aNewPhraseSearches = array_slice($aNewPhraseSearches, 0, 50);
738 // Re-group the searches by their score, junk anything over 20 as just not worth trying
739 $aGroupedSearches = array();
740 foreach($aNewPhraseSearches as $aSearch)
742 if ($aSearch['iSearchRank'] < $iMaxRank)
744 if (!isset($aGroupedSearches[$aSearch['iSearchRank']])) $aGroupedSearches[$aSearch['iSearchRank']] = array();
745 $aGroupedSearches[$aSearch['iSearchRank']][] = $aSearch;
748 ksort($aGroupedSearches);
751 $aSearches = array();
752 foreach($aGroupedSearches as $iScore => $aNewSearches)
754 $iSearchCount += sizeof($aNewSearches);
755 $aSearches = array_merge($aSearches, $aNewSearches);
756 if ($iSearchCount > 50) break;
759 //if (CONST_Debug) _debugDumpGroupedSearches($aGroupedSearches, $aValidTokens);
766 // Re-group the searches by their score, junk anything over 20 as just not worth trying
767 $aGroupedSearches = array();
768 foreach($aSearches as $aSearch)
770 if ($aSearch['iSearchRank'] < $iMaxRank)
772 if (!isset($aGroupedSearches[$aSearch['iSearchRank']])) $aGroupedSearches[$aSearch['iSearchRank']] = array();
773 $aGroupedSearches[$aSearch['iSearchRank']][] = $aSearch;
776 ksort($aGroupedSearches);
779 if (CONST_Debug) var_Dump($aGroupedSearches);
783 $aCopyGroupedSearches = $aGroupedSearches;
784 foreach($aCopyGroupedSearches as $iGroup => $aSearches)
786 foreach($aSearches as $iSearch => $aSearch)
788 if (sizeof($aSearch['aAddress']))
790 $iReverseItem = array_pop($aSearch['aAddress']);
791 if (isset($aPossibleMainWordIDs[$iReverseItem]))
793 $aSearch['aAddress'] = array_merge($aSearch['aAddress'], $aSearch['aName']);
794 $aSearch['aName'] = array($iReverseItem);
795 $aGroupedSearches[$iGroup][] = $aSearch;
797 //$aReverseSearch['aName'][$iReverseItem] = $iReverseItem;
798 //$aGroupedSearches[$iGroup][] = $aReverseSearch;
804 if (CONST_Search_TryDroppedAddressTerms && sizeof($aStructuredQuery) > 0)
806 $aCopyGroupedSearches = $aGroupedSearches;
807 foreach($aCopyGroupedSearches as $iGroup => $aSearches)
809 foreach($aSearches as $iSearch => $aSearch)
811 $aReductionsList = array($aSearch['aAddress']);
812 $iSearchRank = $aSearch['iSearchRank'];
813 while(sizeof($aReductionsList) > 0)
816 if ($iSearchRank > iMaxRank) break 3;
817 $aNewReductionsList = array();
818 foreach($aReductionsList as $aReductionsWordList)
820 for ($iReductionWord = 0; $iReductionWord < sizeof($aReductionsWordList); $iReductionWord++)
822 $aReductionsWordListResult = array_merge(array_slice($aReductionsWordList, 0, $iReductionWord), array_slice($aReductionsWordList, $iReductionWord+1));
823 $aReverseSearch = $aSearch;
824 $aSearch['aAddress'] = $aReductionsWordListResult;
825 $aSearch['iSearchRank'] = $iSearchRank;
826 $aGroupedSearches[$iSearchRank][] = $aReverseSearch;
827 if (sizeof($aReductionsWordListResult) > 0)
829 $aNewReductionsList[] = $aReductionsWordListResult;
833 $aReductionsList = $aNewReductionsList;
837 ksort($aGroupedSearches);
840 // Filter out duplicate searches
841 $aSearchHash = array();
842 foreach($aGroupedSearches as $iGroup => $aSearches)
844 foreach($aSearches as $iSearch => $aSearch)
846 $sHash = serialize($aSearch);
847 if (isset($aSearchHash[$sHash]))
849 unset($aGroupedSearches[$iGroup][$iSearch]);
850 if (sizeof($aGroupedSearches[$iGroup]) == 0) unset($aGroupedSearches[$iGroup]);
854 $aSearchHash[$sHash] = 1;
859 if (CONST_Debug) _debugDumpGroupedSearches($aGroupedSearches, $aValidTokens);
863 foreach($aGroupedSearches as $iGroupedRank => $aSearches)
866 foreach($aSearches as $aSearch)
870 if (CONST_Debug) { echo "<hr><b>Search Loop, group $iGroupLoop, loop $iQueryLoop</b>"; }
871 if (CONST_Debug) _debugDumpGroupedSearches(array($iGroupedRank => array($aSearch)), $aValidTokens);
874 // Must have a location term
875 if (!sizeof($aSearch['aName']) && !sizeof($aSearch['aAddress']) && !$aSearch['fLon'])
877 if ($aSearch['sCountryCode'] && !$aSearch['sClass'] && !$aSearch['sHouseNumber'])
879 if (4 >= $iMinAddressRank && 4 <= $iMaxAddressRank)
881 $sSQL = "select place_id from placex where calculated_country_code='".$aSearch['sCountryCode']."' and rank_search = 4";
882 if ($sCountryCodesSQL) $sSQL .= " and calculated_country_code in ($sCountryCodesSQL)";
883 $sSQL .= " order by st_area(geometry) desc limit 1";
884 if (CONST_Debug) var_dump($sSQL);
885 $aPlaceIDs = $oDB->getCol($sSQL);
890 if (!$bBoundingBoxSearch && !$aSearch['fLon']) continue;
891 if (!$aSearch['sClass']) continue;
892 $sSQL = "select count(*) from pg_tables where tablename = 'place_classtype_".$aSearch['sClass']."_".$aSearch['sType']."'";
893 if ($oDB->getOne($sSQL))
895 $sSQL = "select place_id from place_classtype_".$aSearch['sClass']."_".$aSearch['sType']." ct";
896 if ($sCountryCodesSQL) $sSQL .= " join placex using (place_id)";
897 $sSQL .= " where st_contains($sViewboxSmallSQL, ct.centroid)";
898 if ($sCountryCodesSQL) $sSQL .= " and calculated_country_code in ($sCountryCodesSQL)";
899 if (sizeof($aExcludePlaceIDs))
901 $sSQL .= " and place_id not in (".join(',',$aExcludePlaceIDs).")";
903 if ($sViewboxCentreSQL) $sSQL .= " order by st_distance($sViewboxCentreSQL, ct.centroid) asc";
904 $sSQL .= " limit $iLimit";
905 if (CONST_Debug) var_dump($sSQL);
906 $aPlaceIDs = $oDB->getCol($sSQL);
908 // If excluded place IDs are given, it is fair to assume that
909 // there have been results in the small box, so no further
910 // expansion in that case.
911 if (!sizeof($aPlaceIDs) && !sizeof($aExcludePlaceIDs))
913 $sSQL = "select place_id from place_classtype_".$aSearch['sClass']."_".$aSearch['sType']." ct";
914 if ($sCountryCodesSQL) $sSQL .= " join placex using (place_id)";
915 $sSQL .= " where st_contains($sViewboxLargeSQL, ct.centroid)";
916 if ($sCountryCodesSQL) $sSQL .= " and calculated_country_code in ($sCountryCodesSQL)";
917 if ($sViewboxCentreSQL) $sSQL .= " order by st_distance($sViewboxCentreSQL, ct.centroid) asc";
918 $sSQL .= " limit $iLimit";
919 if (CONST_Debug) var_dump($sSQL);
920 $aPlaceIDs = $oDB->getCol($sSQL);
925 $sSQL = "select place_id from placex where class='".$aSearch['sClass']."' and type='".$aSearch['sType']."'";
926 $sSQL .= " and st_contains($sViewboxSmallSQL, geometry) and linked_place_id is null";
927 if ($sCountryCodesSQL) $sSQL .= " and calculated_country_code in ($sCountryCodesSQL)";
928 if ($sViewboxCentreSQL) $sSQL .= " order by st_distance($sViewboxCentreSQL, centroid) asc";
929 $sSQL .= " limit $iLimit";
930 if (CONST_Debug) var_dump($sSQL);
931 $aPlaceIDs = $oDB->getCol($sSQL);
937 $aPlaceIDs = array();
939 // First we need a position, either aName or fLat or both
943 // TODO: filter out the pointless search terms (2 letter name tokens and less)
944 // they might be right - but they are just too darned expensive to run
945 if (sizeof($aSearch['aName'])) $aTerms[] = "name_vector @> ARRAY[".join($aSearch['aName'],",")."]";
946 if (sizeof($aSearch['aNameNonSearch'])) $aTerms[] = "array_cat(name_vector,ARRAY[]::integer[]) @> ARRAY[".join($aSearch['aNameNonSearch'],",")."]";
947 if (sizeof($aSearch['aAddress']) && $aSearch['aName'] != $aSearch['aAddress'])
949 // For infrequent name terms disable index usage for address
950 if (CONST_Search_NameOnlySearchFrequencyThreshold &&
951 sizeof($aSearch['aName']) == 1 &&
952 $aWordFrequencyScores[$aSearch['aName'][reset($aSearch['aName'])]] < CONST_Search_NameOnlySearchFrequencyThreshold)
954 $aTerms[] = "array_cat(nameaddress_vector,ARRAY[]::integer[]) @> ARRAY[".join(array_merge($aSearch['aAddress'],$aSearch['aAddressNonSearch']),",")."]";
958 $aTerms[] = "nameaddress_vector @> ARRAY[".join($aSearch['aAddress'],",")."]";
959 if (sizeof($aSearch['aAddressNonSearch'])) $aTerms[] = "array_cat(nameaddress_vector,ARRAY[]::integer[]) @> ARRAY[".join($aSearch['aAddressNonSearch'],",")."]";
962 if ($aSearch['sCountryCode']) $aTerms[] = "country_code = '".pg_escape_string($aSearch['sCountryCode'])."'";
963 if ($aSearch['sHouseNumber']) $aTerms[] = "address_rank between 16 and 27";
964 if ($aSearch['fLon'] && $aSearch['fLat'])
966 $aTerms[] = "ST_DWithin(centroid, ST_SetSRID(ST_Point(".$aSearch['fLon'].",".$aSearch['fLat']."),4326), ".$aSearch['fRadius'].")";
967 $aOrder[] = "ST_Distance(centroid, ST_SetSRID(ST_Point(".$aSearch['fLon'].",".$aSearch['fLat']."),4326)) ASC";
969 if (sizeof($aExcludePlaceIDs))
971 $aTerms[] = "place_id not in (".join(',',$aExcludePlaceIDs).")";
973 if ($sCountryCodesSQL)
975 $aTerms[] = "country_code in ($sCountryCodesSQL)";
978 if ($bBoundingBoxSearch) $aTerms[] = "centroid && $sViewboxSmallSQL";
979 if ($sNearPointSQL) $aOrder[] = "ST_Distance($sNearPointSQL, centroid) asc";
981 $sImportanceSQL = '(case when importance = 0 OR importance IS NULL then 0.75-(search_rank::float/40) else importance end)';
982 if ($sViewboxSmallSQL) $sImportanceSQL .= " * case when ST_Contains($sViewboxSmallSQL, centroid) THEN 1 ELSE 0.5 END";
983 if ($sViewboxLargeSQL) $sImportanceSQL .= " * case when ST_Contains($sViewboxLargeSQL, centroid) THEN 1 ELSE 0.5 END";
984 $aOrder[] = "$sImportanceSQL DESC";
985 if (sizeof($aSearch['aFullNameAddress']))
987 $aOrder[] = '(select count(*) from (select unnest(ARRAY['.join($aSearch['aFullNameAddress'],",").']) INTERSECT select unnest(nameaddress_vector))s) DESC';
992 $sSQL = "select place_id";
993 $sSQL .= " from search_name";
994 $sSQL .= " where ".join(' and ',$aTerms);
995 $sSQL .= " order by ".join(', ',$aOrder);
996 if ($aSearch['sHouseNumber'] || $aSearch['sClass'])
997 $sSQL .= " limit 50";
998 elseif (!sizeof($aSearch['aName']) && !sizeof($aSearch['aAddress']) && $aSearch['sClass'])
1001 $sSQL .= " limit ".$iLimit;
1003 if (CONST_Debug) { var_dump($sSQL); }
1004 $aViewBoxPlaceIDs = $oDB->getAll($sSQL);
1005 if (PEAR::IsError($aViewBoxPlaceIDs))
1007 failInternalError("Could not get places for search terms.", $sSQL, $aViewBoxPlaceIDs);
1009 //var_dump($aViewBoxPlaceIDs);
1010 // Did we have an viewbox matches?
1011 $aPlaceIDs = array();
1012 $bViewBoxMatch = false;
1013 foreach($aViewBoxPlaceIDs as $aViewBoxRow)
1015 //if ($bViewBoxMatch == 1 && $aViewBoxRow['in_small'] == 'f') break;
1016 //if ($bViewBoxMatch == 2 && $aViewBoxRow['in_large'] == 'f') break;
1017 //if ($aViewBoxRow['in_small'] == 't') $bViewBoxMatch = 1;
1018 //else if ($aViewBoxRow['in_large'] == 't') $bViewBoxMatch = 2;
1019 $aPlaceIDs[] = $aViewBoxRow['place_id'];
1022 //var_Dump($aPlaceIDs);
1025 if ($aSearch['sHouseNumber'] && sizeof($aPlaceIDs))
1027 $aRoadPlaceIDs = $aPlaceIDs;
1028 $sPlaceIDs = join(',',$aPlaceIDs);
1030 // Now they are indexed look for a house attached to a street we found
1031 $sHouseNumberRegex = '\\\\m'.str_replace(' ','[-,/ ]',$aSearch['sHouseNumber']).'\\\\M';
1032 $sSQL = "select place_id from placex where parent_place_id in (".$sPlaceIDs.") and housenumber ~* E'".$sHouseNumberRegex."'";
1033 if (sizeof($aExcludePlaceIDs))
1035 $sSQL .= " and place_id not in (".join(',',$aExcludePlaceIDs).")";
1037 $sSQL .= " limit $iLimit";
1038 if (CONST_Debug) var_dump($sSQL);
1039 $aPlaceIDs = $oDB->getCol($sSQL);
1041 // If not try the aux fallback table
1043 if (!sizeof($aPlaceIDs))
1045 $sSQL = "select place_id from location_property_aux where parent_place_id in (".$sPlaceIDs.") and housenumber = '".pg_escape_string($aSearch['sHouseNumber'])."'";
1046 if (sizeof($aExcludePlaceIDs))
1048 $sSQL .= " and place_id not in (".join(',',$aExcludePlaceIDs).")";
1050 //$sSQL .= " limit $iLimit";
1051 if (CONST_Debug) var_dump($sSQL);
1052 $aPlaceIDs = $oDB->getCol($sSQL);
1056 if (!sizeof($aPlaceIDs))
1058 $sSQL = "select place_id from location_property_tiger where parent_place_id in (".$sPlaceIDs.") and housenumber = '".pg_escape_string($aSearch['sHouseNumber'])."'";
1059 if (sizeof($aExcludePlaceIDs))
1061 $sSQL .= " and place_id not in (".join(',',$aExcludePlaceIDs).")";
1063 //$sSQL .= " limit $iLimit";
1064 if (CONST_Debug) var_dump($sSQL);
1065 $aPlaceIDs = $oDB->getCol($sSQL);
1068 // Fallback to the road
1069 if (!sizeof($aPlaceIDs) && preg_match('/[0-9]+/', $aSearch['sHouseNumber']))
1071 $aPlaceIDs = $aRoadPlaceIDs;
1076 if ($aSearch['sClass'] && sizeof($aPlaceIDs))
1078 $sPlaceIDs = join(',',$aPlaceIDs);
1079 $aClassPlaceIDs = array();
1081 if (!$aSearch['sOperator'] || $aSearch['sOperator'] == 'name')
1083 // If they were searching for a named class (i.e. 'Kings Head pub') then we might have an extra match
1084 $sSQL = "select place_id from placex where place_id in ($sPlaceIDs) and class='".$aSearch['sClass']."' and type='".$aSearch['sType']."'";
1085 $sSQL .= " and linked_place_id is null";
1086 if ($sCountryCodesSQL) $sSQL .= " and calculated_country_code in ($sCountryCodesSQL)";
1087 $sSQL .= " order by rank_search asc limit $iLimit";
1088 if (CONST_Debug) var_dump($sSQL);
1089 $aClassPlaceIDs = $oDB->getCol($sSQL);
1092 if (!$aSearch['sOperator'] || $aSearch['sOperator'] == 'near') // & in
1094 $sSQL = "select count(*) from pg_tables where tablename = 'place_classtype_".$aSearch['sClass']."_".$aSearch['sType']."'";
1095 $bCacheTable = $oDB->getOne($sSQL);
1097 $sSQL = "select min(rank_search) from placex where place_id in ($sPlaceIDs)";
1099 if (CONST_Debug) var_dump($sSQL);
1100 $iMaxRank = ((int)$oDB->getOne($sSQL));
1102 // For state / country level searches the normal radius search doesn't work very well
1103 $sPlaceGeom = false;
1104 if ($iMaxRank < 9 && $bCacheTable)
1106 // Try and get a polygon to search in instead
1107 $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";
1108 if (CONST_Debug) var_dump($sSQL);
1109 $sPlaceGeom = $oDB->getOne($sSQL);
1119 $sSQL = "select place_id from placex where place_id in ($sPlaceIDs) and rank_search < $iMaxRank";
1120 if (CONST_Debug) var_dump($sSQL);
1121 $aPlaceIDs = $oDB->getCol($sSQL);
1122 $sPlaceIDs = join(',',$aPlaceIDs);
1125 if ($sPlaceIDs || $sPlaceGeom)
1131 // More efficient - can make the range bigger
1135 if ($sNearPointSQL) $sOrderBySQL = "ST_Distance($sNearPointSQL, l.centroid)";
1136 else if ($sPlaceIDs) $sOrderBySQL = "ST_Distance(l.centroid, f.geometry)";
1137 else if ($sPlaceGeom) $sOrderBysSQL = "ST_Distance(st_centroid('".$sPlaceGeom."'), l.centroid)";
1139 $sSQL = "select distinct l.place_id".($sOrderBySQL?','.$sOrderBySQL:'')." from place_classtype_".$aSearch['sClass']."_".$aSearch['sType']." as l";
1140 if ($sCountryCodesSQL) $sSQL .= " join placex as lp using (place_id)";
1143 $sSQL .= ",placex as f where ";
1144 $sSQL .= "f.place_id in ($sPlaceIDs) and ST_DWithin(l.centroid, f.centroid, $fRange) ";
1149 $sSQL .= "ST_Contains('".$sPlaceGeom."', l.centroid) ";
1151 if (sizeof($aExcludePlaceIDs))
1153 $sSQL .= " and l.place_id not in (".join(',',$aExcludePlaceIDs).")";
1155 if ($sCountryCodesSQL) $sSQL .= " and lp.calculated_country_code in ($sCountryCodesSQL)";
1156 if ($sOrderBySQL) $sSQL .= "order by ".$sOrderBySQL." asc";
1157 if ($iOffset) $sSQL .= " offset $iOffset";
1158 $sSQL .= " limit $iLimit";
1159 if (CONST_Debug) var_dump($sSQL);
1160 $aClassPlaceIDs = array_merge($aClassPlaceIDs, $oDB->getCol($sSQL));
1164 if (isset($aSearch['fRadius']) && $aSearch['fRadius']) $fRange = $aSearch['fRadius'];
1167 if ($sNearPointSQL) $sOrderBySQL = "ST_Distance($sNearPointSQL, l.geometry)";
1168 else $sOrderBySQL = "ST_Distance(l.geometry, f.geometry)";
1170 $sSQL = "select distinct l.place_id".($sOrderBysSQL?','.$sOrderBysSQL:'')." from placex as l,placex as f where ";
1171 $sSQL .= "f.place_id in ( $sPlaceIDs) and ST_DWithin(l.geometry, f.centroid, $fRange) ";
1172 $sSQL .= "and l.class='".$aSearch['sClass']."' and l.type='".$aSearch['sType']."' ";
1173 if (sizeof($aExcludePlaceIDs))
1175 $sSQL .= " and l.place_id not in (".join(',',$aExcludePlaceIDs).")";
1177 if ($sCountryCodesSQL) $sSQL .= " and l.calculated_country_code in ($sCountryCodesSQL)";
1178 if ($sOrderBy) $sSQL .= "order by ".$OrderBysSQL." asc";
1179 if ($iOffset) $sSQL .= " offset $iOffset";
1180 $sSQL .= " limit $iLimit";
1181 if (CONST_Debug) var_dump($sSQL);
1182 $aClassPlaceIDs = array_merge($aClassPlaceIDs, $oDB->getCol($sSQL));
1187 $aPlaceIDs = $aClassPlaceIDs;
1193 if (PEAR::IsError($aPlaceIDs))
1195 failInternalError("Could not get place IDs from tokens." ,$sSQL, $aPlaceIDs);
1198 if (CONST_Debug) { echo "<br><b>Place IDs:</b> "; var_Dump($aPlaceIDs); }
1200 foreach($aPlaceIDs as $iPlaceID)
1202 $aResultPlaceIDs[$iPlaceID] = $iPlaceID;
1204 if ($iQueryLoop > 20) break;
1207 if (isset($aResultPlaceIDs) && sizeof($aResultPlaceIDs) && ($iMinAddressRank != 0 || $iMaxAddressRank != 30))
1209 // Need to verify passes rank limits before dropping out of the loop (yuk!)
1210 $sSQL = "select place_id from placex where place_id in (".join(',',$aResultPlaceIDs).") ";
1211 $sSQL .= "and (placex.rank_address between $iMinAddressRank and $iMaxAddressRank ";
1212 if (14 >= $iMinAddressRank && 14 <= $iMaxAddressRank) $sSQL .= " OR (extratags->'place') = 'city'";
1213 if ($aAddressRankList) $sSQL .= " OR placex.rank_address in (".join(',',$aAddressRankList).")";
1214 $sSQL .= ") UNION select place_id from location_property_tiger where place_id in (".join(',',$aResultPlaceIDs).") ";
1215 $sSQL .= "and (30 between $iMinAddressRank and $iMaxAddressRank ";
1216 if ($aAddressRankList) $sSQL .= " OR 30 in (".join(',',$aAddressRankList).")";
1218 if (CONST_Debug) var_dump($sSQL);
1219 $aResultPlaceIDs = $oDB->getCol($sSQL);
1224 if (isset($aResultPlaceIDs) && sizeof($aResultPlaceIDs)) break;
1225 if ($iGroupLoop > 4) break;
1226 if ($iQueryLoop > 30) break;
1229 // Did we find anything?
1230 if (isset($aResultPlaceIDs) && sizeof($aResultPlaceIDs))
1232 //var_Dump($aResultPlaceIDs);exit;
1233 // Get the details for display (is this a redundant extra step?)
1234 $sPlaceIDs = join(',',$aResultPlaceIDs);
1235 $sImportanceSQL = '';
1236 if ($sViewboxSmallSQL) $sImportanceSQL .= " case when ST_Contains($sViewboxSmallSQL, ST_Collect(centroid)) THEN 1 ELSE 0.75 END * ";
1237 if ($sViewboxLargeSQL) $sImportanceSQL .= " case when ST_Contains($sViewboxLargeSQL, ST_Collect(centroid)) THEN 1 ELSE 0.75 END * ";
1239 $sOrderSQL = 'CASE ';
1240 foreach(array_keys($aResultPlaceIDs) as $iOrder => $iPlaceID)
1242 $sOrderSQL .= 'when min(place_id) = '.$iPlaceID.' then '.$iOrder.' ';
1244 $sOrderSQL .= ' ELSE 10000000 END';
1245 $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,";
1246 $sSQL .= "get_address_by_language(place_id, $sLanguagePrefArraySQL) as langaddress,";
1247 $sSQL .= "get_name_by_language(name, $sLanguagePrefArraySQL) as placename,";
1248 $sSQL .= "get_name_by_language(name, ARRAY['ref']) as ref,";
1249 $sSQL .= "avg(ST_X(centroid)) as lon,avg(ST_Y(centroid)) as lat, ";
1250 //$sSQL .= $sOrderSQL." as porder, ";
1251 $sSQL .= $sImportanceSQL."coalesce(importance,0.75-(rank_search::float/40)) as importance, ";
1252 $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, ";
1253 $sSQL .= "(extratags->'place') as extra_place ";
1254 $sSQL .= "from placex where place_id in ($sPlaceIDs) ";
1255 $sSQL .= "and (placex.rank_address between $iMinAddressRank and $iMaxAddressRank ";
1256 if (14 >= $iMinAddressRank && 14 <= $iMaxAddressRank) $sSQL .= " OR (extratags->'place') = 'city'";
1257 if ($aAddressRankList) $sSQL .= " OR placex.rank_address in (".join(',',$aAddressRankList).")";
1259 if ($sAllowedTypesSQLList) $sSQL .= "and placex.class in $sAllowedTypesSQLList ";
1260 $sSQL .= "and linked_place_id is null ";
1261 $sSQL .= "group by osm_type,osm_id,class,type,admin_level,rank_search,rank_address,calculated_country_code,importance";
1262 if (!$bDeDupe) $sSQL .= ",place_id";
1263 $sSQL .= ",langaddress ";
1264 $sSQL .= ",placename ";
1266 $sSQL .= ",extratags->'place' ";
1268 $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,";
1269 $sSQL .= "get_address_by_language(place_id, $sLanguagePrefArraySQL) as langaddress,";
1270 $sSQL .= "null as placename,";
1271 $sSQL .= "null as ref,";
1272 $sSQL .= "avg(ST_X(centroid)) as lon,avg(ST_Y(centroid)) as lat, ";
1273 //$sSQL .= $sOrderSQL." as porder, ";
1274 $sSQL .= $sImportanceSQL."0.015 as importance, ";
1275 $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, ";
1276 $sSQL .= "null as extra_place ";
1277 $sSQL .= "from location_property_tiger where place_id in ($sPlaceIDs) ";
1278 $sSQL .= "and 30 between $iMinAddressRank and $iMaxAddressRank ";
1279 $sSQL .= "group by place_id";
1280 if (!$bDeDupe) $sSQL .= ",place_id";
1283 $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,";
1284 $sSQL .= "get_address_by_language(place_id, $sLanguagePrefArraySQL) as langaddress,";
1285 $sSQL .= "null as placename,";
1286 $sSQL .= "null as ref,";
1287 $sSQL .= "avg(ST_X(centroid)) as lon,avg(ST_Y(centroid)) as lat, ";
1288 //$sSQL .= $sOrderSQL." as porder, ";
1289 $sSQL .= $sImportanceSQL."0.01 as importance, ";
1290 $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, ";
1291 $sSQL .= "null as extra_place ";
1292 $sSQL .= "from location_property_aux where place_id in ($sPlaceIDs) ";
1293 $sSQL .= "and 30 between $iMinAddressRank and $iMaxAddressRank ";
1294 $sSQL .= "group by place_id";
1295 if (!$bDeDupe) $sSQL .= ",place_id";
1296 $sSQL .= ",get_address_by_language(place_id, $sLanguagePrefArraySQL) ";
1297 $sSQL .= "order by importance desc";
1298 //$sSQL .= "order by rank_search,rank_address,porder asc";
1300 if (CONST_Debug) { echo "<hr>"; var_dump($sSQL); }
1301 $aSearchResults = $oDB->getAll($sSQL);
1302 //var_dump($sSQL,$aSearchResults);exit;
1304 if (PEAR::IsError($aSearchResults))
1306 failInternalError("Could not get details for place.", $sSQL, $aSearchResults);
1309 } // end if ($sQuery)
1312 if (isset($_GET['nearlat']) && trim($_GET['nearlat'])!=='' && isset($_GET['nearlon']) && trim($_GET['nearlon']) !== '')
1314 $iPlaceID = geocodeReverse((float)$_GET['nearlat'], (float)$_GET['nearlon']);
1318 $aResultPlaceIDs = array($iPlaceID);
1319 // TODO: this needs refactoring!
1321 // Get the details for display (is this a redundant extra step?)
1322 $sPlaceIDs = join(',',$aResultPlaceIDs);
1323 $sOrderSQL = 'CASE ';
1324 foreach(array_keys($aResultPlaceIDs) as $iOrder => $iPlaceID)
1326 $sOrderSQL .= 'when min(place_id) = '.$iPlaceID.' then '.$iOrder.' ';
1328 $sOrderSQL .= ' ELSE 10000000 END';
1329 $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,";
1330 $sSQL .= "get_address_by_language(place_id, $sLanguagePrefArraySQL) as langaddress,";
1331 $sSQL .= "get_name_by_language(name, $sLanguagePrefArraySQL) as placename,";
1332 $sSQL .= "get_name_by_language(name, ARRAY['ref']) as ref,";
1333 $sSQL .= "avg(ST_X(centroid)) as lon,avg(ST_Y(centroid)) as lat, ";
1334 //$sSQL .= $sOrderSQL." as porder, ";
1335 $sSQL .= "coalesce(importance,0.75-(rank_search::float/40)) as importance, ";
1336 $sSQL .= "(extratags->'place') as extra_place ";
1337 $sSQL .= "from placex where place_id in ($sPlaceIDs) ";
1338 $sSQL .= "and (placex.rank_address between $iMinAddressRank and $iMaxAddressRank ";
1339 if (14 >= $iMinAddressRank && 14 <= $iMaxAddressRank) $sSQL .= " OR (extratags->'place') = 'city'";
1340 if ($aAddressRankList) $sSQL .= " OR placex.rank_address in (".join(',',$aAddressRankList).")";
1342 $sSQL .= "group by osm_type,osm_id,class,type,admin_level,rank_search,rank_address,calculated_country_code,importance";
1343 if (!$bDeDupe) $sSQL .= ",place_id";
1344 $sSQL .= ",get_address_by_language(place_id, $sLanguagePrefArraySQL) ";
1345 $sSQL .= ",get_name_by_language(name, $sLanguagePrefArraySQL) ";
1346 $sSQL .= ",get_name_by_language(name, ARRAY['ref']) ";
1347 $sSQL .= ",extratags->'place' ";
1349 $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,";
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.15 as importance, ";
1356 $sSQL .= "null as extra_place ";
1357 $sSQL .= "from location_property_tiger 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";
1363 $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,";
1364 $sSQL .= "get_address_by_language(place_id, $sLanguagePrefArraySQL) as langaddress,";
1365 $sSQL .= "null as placename,";
1366 $sSQL .= "null as ref,";
1367 $sSQL .= "avg(ST_X(centroid)) as lon,avg(ST_Y(centroid)) as lat, ";
1368 //$sSQL .= $sOrderSQL." as porder, ";
1369 $sSQL .= "-0.10 as importance, ";
1370 $sSQL .= "null as extra_place ";
1371 $sSQL .= "from location_property_aux where place_id in ($sPlaceIDs) ";
1372 $sSQL .= "and 30 between $iMinAddressRank and $iMaxAddressRank ";
1373 $sSQL .= "group by place_id";
1375 if (!$bDeDupe) $sSQL .= ",place_id";
1376 $sSQL .= ",get_address_by_language(place_id, $sLanguagePrefArraySQL) ";
1377 $sSQL .= "order by importance desc";
1378 //$sSQL .= "order by rank_search,rank_address,porder asc";
1379 if (CONST_Debug) { echo "<hr>", var_dump($sSQL); }
1380 $aSearchResults = $oDB->getAll($sSQL);
1381 //var_dump($sSQL,$aSearchResults);exit;
1383 if (PEAR::IsError($aSearchResults))
1385 failInternalError("Could not get details for place (near).", $sSQL, $aSearchResults);
1390 $aSearchResults = array();
1396 $sSearchResult = '';
1397 if (!sizeof($aSearchResults) && isset($_GET['q']) && $_GET['q'])
1399 $sSearchResult = 'No Results Found';
1401 //var_Dump($aSearchResults);
1403 $aClassType = getClassTypesWithImportance();
1404 $aRecheckWords = preg_split('/\b/u',$sQuery);
1405 foreach($aRecheckWords as $i => $sWord)
1407 if (!$sWord) unset($aRecheckWords[$i]);
1409 foreach($aSearchResults as $iResNum => $aResult)
1411 if (CONST_Search_AreaPolygons)
1413 // Get the bounding box and outline polygon
1414 $sSQL = "select place_id,numfeatures,area,outline,";
1415 $sSQL .= "ST_Y(ST_PointN(ST_ExteriorRing(Box2D(outline)),4)) as minlat,ST_Y(ST_PointN(ST_ExteriorRing(Box2D(outline)),2)) as maxlat,";
1416 $sSQL .= "ST_X(ST_PointN(ST_ExteriorRing(Box2D(outline)),1)) as minlon,ST_X(ST_PointN(ST_ExteriorRing(Box2D(outline)),3)) as maxlon,";
1417 $sSQL .= "ST_AsText(outline) as outlinestring from get_place_boundingbox_quick(".$aResult['place_id'].")";
1419 $sSQL = "select place_id,0 as numfeatures,st_area(geometry) as area,";
1420 $sSQL .= "ST_Y(centroid) as centrelat,ST_X(centroid) as centrelon,";
1421 $sSQL .= "ST_Y(ST_PointN(ST_ExteriorRing(Box2D(geometry)),4)) as minlat,ST_Y(ST_PointN(ST_ExteriorRing(Box2D(geometry)),2)) as maxlat,";
1422 $sSQL .= "ST_X(ST_PointN(ST_ExteriorRing(Box2D(geometry)),1)) as minlon,ST_X(ST_PointN(ST_ExteriorRing(Box2D(geometry)),3)) as maxlon";
1423 if ($bAsGeoJSON) $sSQL .= ",ST_AsGeoJSON(geometry) as asgeojson";
1424 if ($bAsKML) $sSQL .= ",ST_AsKML(geometry) as askml";
1425 if ($bAsSVG) $sSQL .= ",ST_AsSVG(geometry) as assvg";
1426 if ($bAsText || $bShowPolygons) $sSQL .= ",ST_AsText(geometry) as astext";
1427 $sSQL .= " from placex where place_id = ".$aResult['place_id'].' and st_geometrytype(Box2D(geometry)) = \'ST_Polygon\'';
1428 $aPointPolygon = $oDB->getRow($sSQL);
1429 if (PEAR::IsError($aPointPolygon))
1431 failInternalError("Could not get outline.", $sSQL, $aPointPolygon);
1433 if ($aPointPolygon['place_id'])
1435 if ($bAsGeoJSON) $aResult['asgeojson'] = $aPointPolygon['asgeojson'];
1436 if ($bAsKML) $aResult['askml'] = $aPointPolygon['askml'];
1437 if ($bAsSVG) $aResult['assvg'] = $aPointPolygon['assvg'];
1438 if ($bAsText) $aResult['astext'] = $aPointPolygon['astext'];
1440 if ($aPointPolygon['centrelon'] !== null && $aPointPolygon['centrelat'] !== null )
1442 $aResult['lat'] = $aPointPolygon['centrelat'];
1443 $aResult['lon'] = $aPointPolygon['centrelon'];
1447 // Translate geometary string to point array
1448 if (preg_match('#POLYGON\\(\\(([- 0-9.,]+)#',$aPointPolygon['astext'],$aMatch))
1450 preg_match_all('/(-?[0-9.]+) (-?[0-9.]+)/',$aMatch[1],$aPolyPoints,PREG_SET_ORDER);
1452 /*elseif (preg_match('#MULTIPOLYGON\\(\\(\\(([- 0-9.,]+)#',$aPointPolygon['astext'],$aMatch))
1454 preg_match_all('/(-?[0-9.]+) (-?[0-9.]+)/',$aMatch[1],$aPolyPoints,PREG_SET_ORDER);
1456 elseif (preg_match('#POINT\\((-?[0-9.]+) (-?[0-9.]+)\\)#',$aPointPolygon['astext'],$aMatch))
1459 $iSteps = ($fRadius * 40000)^2;
1460 $fStepSize = (2*pi())/$iSteps;
1461 $aPolyPoints = array();
1462 for($f = 0; $f < 2*pi(); $f += $fStepSize)
1464 $aPolyPoints[] = array('',$aMatch[1]+($fRadius*sin($f)),$aMatch[2]+($fRadius*cos($f)));
1466 $aPointPolygon['minlat'] = $aPointPolygon['minlat'] - $fRadius;
1467 $aPointPolygon['maxlat'] = $aPointPolygon['maxlat'] + $fRadius;
1468 $aPointPolygon['minlon'] = $aPointPolygon['minlon'] - $fRadius;
1469 $aPointPolygon['maxlon'] = $aPointPolygon['maxlon'] + $fRadius;
1473 // Output data suitable for display (points and a bounding box)
1474 if ($bShowPolygons && isset($aPolyPoints))
1476 $aResult['aPolyPoints'] = array();
1477 foreach($aPolyPoints as $aPoint)
1479 $aResult['aPolyPoints'][] = array($aPoint[1], $aPoint[2]);
1482 $aResult['aBoundingBox'] = array($aPointPolygon['minlat'],$aPointPolygon['maxlat'],$aPointPolygon['minlon'],$aPointPolygon['maxlon']);
1486 if ($aResult['extra_place'] == 'city')
1488 $aResult['class'] = 'place';
1489 $aResult['type'] = 'city';
1490 $aResult['rank_search'] = 16;
1493 if (!isset($aResult['aBoundingBox']))
1496 $fDiameter = 0.0001;
1498 if (isset($aClassType[$aResult['class'].':'.$aResult['type'].':'.$aResult['admin_level']]['defdiameter'])
1499 && $aClassType[$aResult['class'].':'.$aResult['type'].':'.$aResult['admin_level']]['defdiameter'])
1501 $fDiameter = $aClassType[$aResult['class'].':'.$aResult['type'].':'.$aResult['admin_level']]['defzoom'];
1503 elseif (isset($aClassType[$aResult['class'].':'.$aResult['type']]['defdiameter'])
1504 && $aClassType[$aResult['class'].':'.$aResult['type']]['defdiameter'])
1506 $fDiameter = $aClassType[$aResult['class'].':'.$aResult['type']]['defdiameter'];
1508 $fRadius = $fDiameter / 2;
1510 $iSteps = max(8,min(100,$fRadius * 3.14 * 100000));
1511 $fStepSize = (2*pi())/$iSteps;
1512 $aPolyPoints = array();
1513 for($f = 0; $f < 2*pi(); $f += $fStepSize)
1515 $aPolyPoints[] = array('',$aResult['lon']+($fRadius*sin($f)),$aResult['lat']+($fRadius*cos($f)));
1517 $aPointPolygon['minlat'] = $aResult['lat'] - $fRadius;
1518 $aPointPolygon['maxlat'] = $aResult['lat'] + $fRadius;
1519 $aPointPolygon['minlon'] = $aResult['lon'] - $fRadius;
1520 $aPointPolygon['maxlon'] = $aResult['lon'] + $fRadius;
1522 // Output data suitable for display (points and a bounding box)
1525 $aResult['aPolyPoints'] = array();
1526 foreach($aPolyPoints as $aPoint)
1528 $aResult['aPolyPoints'][] = array($aPoint[1], $aPoint[2]);
1531 $aResult['aBoundingBox'] = array($aPointPolygon['minlat'],$aPointPolygon['maxlat'],$aPointPolygon['minlon'],$aPointPolygon['maxlon']);
1534 // Is there an icon set for this type of result?
1535 if (isset($aClassType[$aResult['class'].':'.$aResult['type']]['icon'])
1536 && $aClassType[$aResult['class'].':'.$aResult['type']]['icon'])
1538 $aResult['icon'] = CONST_Website_BaseURL.'images/mapicons/'.$aClassType[$aResult['class'].':'.$aResult['type']]['icon'].'.p.20.png';
1541 if (isset($aClassType[$aResult['class'].':'.$aResult['type']]['label'])
1542 && $aClassType[$aResult['class'].':'.$aResult['type']]['label'])
1544 $aResult['label'] = $aClassType[$aResult['class'].':'.$aResult['type']]['label'];
1547 if ($bShowAddressDetails)
1549 $aResult['address'] = getAddressDetails($oDB, $sLanguagePrefArraySQL, $aResult['place_id'], $aResult['country_code']);
1550 if ($aResult['extra_place'] == 'city' && !isset($aResult['address']['city']))
1552 $aResult['address'] = array_merge(array('city' => array_shift(array_values($aResult['address']))), $aResult['address']);
1555 //var_dump($aResult['address']);
1559 // Adjust importance for the number of exact string matches in the result
1560 $aResult['importance'] = max(0.001,$aResult['importance']);
1562 $sAddress = $aResult['langaddress'];
1563 foreach($aRecheckWords as $i => $sWord)
1565 if (stripos($sAddress, $sWord)!==false) $iCountWords++;
1568 $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
1570 //if (CONST_Debug) var_dump($aResult['class'].':'.$aResult['type'].':'.$aResult['admin_level']);
1572 if (isset($aClassType[$aResult['class'].':'.$aResult['type'].':'.$aResult['admin_level']]['importance'])
1573 && $aClassType[$aResult['class'].':'.$aResult['type'].':'.$aResult['admin_level']]['importance'])
1575 $aResult['importance'] = $aClassType[$aResult['class'].':'.$aResult['type'].':'.$aResult['admin_level']]['importance'];
1577 elseif (isset($aClassType[$aResult['class'].':'.$aResult['type']]['importance'])
1578 && $aClassType[$aResult['class'].':'.$aResult['type']]['importance'])
1580 $aResult['importance'] = $aClassType[$aResult['class'].':'.$aResult['type']]['importance'];
1584 $aResult['importance'] = 1000000000000000;
1587 $aResult['name'] = $aResult['langaddress'];
1588 $aResult['foundorder'] = -$aResult['addressimportance'];
1589 $aSearchResults[$iResNum] = $aResult;
1591 uasort($aSearchResults, 'byImportance');
1593 $aOSMIDDone = array();
1594 $aClassTypeNameDone = array();
1595 $aToFilter = $aSearchResults;
1596 $aSearchResults = array();
1599 foreach($aToFilter as $iResNum => $aResult)
1601 if ($aResult['type'] == 'adminitrative') $aResult['type'] = 'administrative';
1602 $aExcludePlaceIDs[$aResult['place_id']] = $aResult['place_id'];
1605 $fLat = $aResult['lat'];
1606 $fLon = $aResult['lon'];
1607 if (isset($aResult['zoom'])) $iZoom = $aResult['zoom'];
1610 if (!$bDeDupe || (!isset($aOSMIDDone[$aResult['osm_type'].$aResult['osm_id']])
1611 && !isset($aClassTypeNameDone[$aResult['osm_type'].$aResult['class'].$aResult['type'].$aResult['name'].$aResult['admin_level']])))
1613 $aOSMIDDone[$aResult['osm_type'].$aResult['osm_id']] = true;
1614 $aClassTypeNameDone[$aResult['osm_type'].$aResult['class'].$aResult['type'].$aResult['name'].$aResult['admin_level']] = true;
1615 $aSearchResults[] = $aResult;
1618 // Absolute limit on number of results
1619 if (sizeof($aSearchResults) >= $iFinalLimit) break;
1622 $sDataDate = $oDB->getOne("select TO_CHAR(lastimportdate - '2 minutes'::interval,'YYYY/MM/DD HH24:MI')||' GMT' from import_status limit 1");
1624 if (isset($_GET['nearlat']) && isset($_GET['nearlon']))
1626 $sQuery .= ' ['.$_GET['nearlat'].','.$_GET['nearlon'].']';
1631 logEnd($oDB, $hLog, sizeof($aToFilter));
1633 $sMoreURL = CONST_Website_BaseURL.'search?format='.urlencode($sOutputFormat).'&exclude_place_ids='.join(',',$aExcludePlaceIDs);
1634 if (isset($_SERVER["HTTP_ACCEPT_LANGUAGE"])) $sMoreURL .= '&accept-language='.$_SERVER["HTTP_ACCEPT_LANGUAGE"];
1635 if ($bShowPolygons) $sMoreURL .= '&polygon=1';
1636 if ($bShowAddressDetails) $sMoreURL .= '&addressdetails=1';
1637 if (isset($_GET['viewbox']) && $_GET['viewbox']) $sMoreURL .= '&viewbox='.urlencode($_GET['viewbox']);
1638 if (isset($_GET['nearlat']) && isset($_GET['nearlon'])) $sMoreURL .= '&nearlat='.(float)$_GET['nearlat'].'&nearlon='.(float)$_GET['nearlon'];
1639 $sMoreURL .= '&q='.urlencode($sQuery);
1641 if (CONST_Debug) exit;
1643 include(CONST_BasePath.'/lib/template/search-'.$sOutputFormat.'.php');