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.]*)(\\]|$|\\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;
452 foreach($aTokens as $sToken)
454 // Unknown single word token with a number - assume it is a house number
455 if (!isset($aValidTokens[' '.$sToken]) && strpos($sToken,' ') === false && preg_match('/[0-9]/', $sToken))
457 $aValidTokens[' '.$sToken] = array(array('class'=>'place','type'=>'house'));
461 // Any words that have failed completely?
464 // Start the search process
465 $aResultPlaceIDs = array();
468 Calculate all searches using aValidTokens i.e.
469 'Wodsworth Road, Sheffield' =>
473 0 1 (wodsworth)(road)
476 Score how good the search is so they can be ordered
478 foreach($aPhrases as $iPhrase => $sPhrase)
480 $aNewPhraseSearches = array();
481 if ($bStructuredPhrases) $sPhraseType = $aPhraseTypes[$iPhrase];
482 else $sPhraseType = '';
484 foreach($aPhrases[$iPhrase]['wordsets'] as $aWordset)
486 $aWordsetSearches = $aSearches;
488 // Add all words from this wordset
489 foreach($aWordset as $iToken => $sToken)
491 //echo "<br><b>$sToken</b>";
492 $aNewWordsetSearches = array();
494 foreach($aWordsetSearches as $aCurrentSearch)
497 //var_dump($aCurrentSearch);
500 // If the token is valid
501 if (isset($aValidTokens[' '.$sToken]))
503 foreach($aValidTokens[' '.$sToken] as $aSearchTerm)
505 $aSearch = $aCurrentSearch;
506 $aSearch['iSearchRank']++;
507 if (($sPhraseType == '' || $sPhraseType == 'country') && !empty($aSearchTerm['country_code']) && $aSearchTerm['country_code'] != '0')
509 if ($aSearch['sCountryCode'] === false)
511 $aSearch['sCountryCode'] = strtolower($aSearchTerm['country_code']);
512 // Country is almost always at the end of the string - increase score for finding it anywhere else (optimisation)
513 // If reverse order is enabled, it may appear at the beginning as well.
514 if (($iToken+1 != sizeof($aWordset) || $iPhrase+1 != sizeof($aPhrases)) &&
515 (!$bReverseInPlan || $iToken > 0 || $iPhrase > 0))
517 $aSearch['iSearchRank'] += 5;
519 if ($aSearch['iSearchRank'] < $iMaxRank) $aNewWordsetSearches[] = $aSearch;
522 elseif (isset($aSearchTerm['lat']) && $aSearchTerm['lat'] !== '' && $aSearchTerm['lat'] !== null)
524 if ($aSearch['fLat'] === '')
526 $aSearch['fLat'] = $aSearchTerm['lat'];
527 $aSearch['fLon'] = $aSearchTerm['lon'];
528 $aSearch['fRadius'] = $aSearchTerm['radius'];
529 if ($aSearch['iSearchRank'] < $iMaxRank) $aNewWordsetSearches[] = $aSearch;
532 elseif ($sPhraseType == 'postalcode')
534 // 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
535 if (isset($aSearchTerm['word_id']) && $aSearchTerm['word_id'])
537 // If we already have a name try putting the postcode first
538 if (sizeof($aSearch['aName']))
540 $aNewSearch = $aSearch;
541 $aNewSearch['aAddress'] = array_merge($aNewSearch['aAddress'], $aNewSearch['aName']);
542 $aNewSearch['aName'] = array();
543 $aNewSearch['aName'][$aSearchTerm['word_id']] = $aSearchTerm['word_id'];
544 if ($aSearch['iSearchRank'] < $iMaxRank) $aNewWordsetSearches[] = $aNewSearch;
547 if (sizeof($aSearch['aName']))
549 if ((!$bStructuredPhrases || $iPhrase > 0) && $sPhraseType != 'country' && (!isset($aValidTokens[$sToken]) || strlen($sToken) < 4 || strpos($sToken, ' ') !== false))
551 $aSearch['aAddress'][$aSearchTerm['word_id']] = $aSearchTerm['word_id'];
555 $aCurrentSearch['aFullNameAddress'][$aSearchTerm['word_id']] = $aSearchTerm['word_id'];
556 $aSearch['iSearchRank'] += 1000; // skip;
561 $aSearch['aName'][$aSearchTerm['word_id']] = $aSearchTerm['word_id'];
562 //$aSearch['iNamePhrase'] = $iPhrase;
564 if ($aSearch['iSearchRank'] < $iMaxRank) $aNewWordsetSearches[] = $aSearch;
568 elseif (($sPhraseType == '' || $sPhraseType == 'street') && $aSearchTerm['class'] == 'place' && $aSearchTerm['type'] == 'house')
570 if ($aSearch['sHouseNumber'] === '')
572 $aSearch['sHouseNumber'] = $sToken;
573 if ($aSearch['iSearchRank'] < $iMaxRank) $aNewWordsetSearches[] = $aSearch;
575 // Fall back to not searching for this item (better than nothing)
576 $aSearch = $aCurrentSearch;
577 $aSearch['iSearchRank'] += 1;
578 if ($aSearch['iSearchRank'] < $iMaxRank) $aNewWordsetSearches[] = $aSearch;
582 elseif ($sPhraseType == '' && $aSearchTerm['class'] !== '' && $aSearchTerm['class'] !== null)
584 if ($aSearch['sClass'] === '')
586 $aSearch['sOperator'] = $aSearchTerm['operator'];
587 $aSearch['sClass'] = $aSearchTerm['class'];
588 $aSearch['sType'] = $aSearchTerm['type'];
589 if (sizeof($aSearch['aName'])) $aSearch['sOperator'] = 'name';
590 else $aSearch['sOperator'] = 'near'; // near = in for the moment
592 // Do we have a shortcut id?
593 if ($aSearch['sOperator'] == 'name')
595 $sSQL = "select get_tagpair('".$aSearch['sClass']."', '".$aSearch['sType']."')";
596 if ($iAmenityID = $oDB->getOne($sSQL))
598 $aValidTokens[$aSearch['sClass'].':'.$aSearch['sType']] = array('word_id' => $iAmenityID);
599 $aSearch['aName'][$iAmenityID] = $iAmenityID;
600 $aSearch['sClass'] = '';
601 $aSearch['sType'] = '';
604 if ($aSearch['iSearchRank'] < $iMaxRank) $aNewWordsetSearches[] = $aSearch;
607 elseif (isset($aSearchTerm['word_id']) && $aSearchTerm['word_id'])
609 if (sizeof($aSearch['aName']))
611 if ((!$bStructuredPhrases || $iPhrase > 0) && $sPhraseType != 'country' && (!isset($aValidTokens[$sToken]) || strlen($sToken) < 4 || strpos($sToken, ' ') !== false))
613 $aSearch['aAddress'][$aSearchTerm['word_id']] = $aSearchTerm['word_id'];
617 $aCurrentSearch['aFullNameAddress'][$aSearchTerm['word_id']] = $aSearchTerm['word_id'];
618 $aSearch['iSearchRank'] += 1000; // skip;
623 $aSearch['aName'][$aSearchTerm['word_id']] = $aSearchTerm['word_id'];
624 //$aSearch['iNamePhrase'] = $iPhrase;
626 if ($aSearch['iSearchRank'] < $iMaxRank) $aNewWordsetSearches[] = $aSearch;
630 if (isset($aValidTokens[$sToken]))
632 // Allow searching for a word - but at extra cost
633 foreach($aValidTokens[$sToken] as $aSearchTerm)
635 if (isset($aSearchTerm['word_id']) && $aSearchTerm['word_id'])
637 if ((!$bStructuredPhrases || $iPhrase > 0) && sizeof($aCurrentSearch['aName']) && strlen($sToken) >= 4)
639 $aSearch = $aCurrentSearch;
640 $aSearch['iSearchRank'] += 1;
641 if ($aWordFrequencyScores[$aSearchTerm['word_id']] < CONST_Max_Word_Frequency)
643 $aSearch['aAddress'][$aSearchTerm['word_id']] = $aSearchTerm['word_id'];
644 if ($aSearch['iSearchRank'] < $iMaxRank) $aNewWordsetSearches[] = $aSearch;
646 elseif (isset($aValidTokens[' '.$sToken])) // revert to the token version?
648 foreach($aValidTokens[' '.$sToken] as $aSearchTermToken)
650 if (empty($aSearchTermToken['country_code'])
651 && empty($aSearchTermToken['lat'])
652 && empty($aSearchTermToken['class']))
654 $aSearch = $aCurrentSearch;
655 $aSearch['iSearchRank'] += 1;
656 $aSearch['aAddress'][$aSearchTermToken['word_id']] = $aSearchTermToken['word_id'];
657 if ($aSearch['iSearchRank'] < $iMaxRank) $aNewWordsetSearches[] = $aSearch;
663 $aSearch['aAddressNonSearch'][$aSearchTerm['word_id']] = $aSearchTerm['word_id'];
664 if ($aSearch['iSearchRank'] < $iMaxRank) $aNewWordsetSearches[] = $aSearch;
668 if (!sizeof($aCurrentSearch['aName']) || $aCurrentSearch['iNamePhrase'] == $iPhrase)
670 $aSearch = $aCurrentSearch;
671 $aSearch['iSearchRank'] += 2;
672 if (preg_match('#^[0-9]+$#', $sToken)) $aSearch['iSearchRank'] += 2;
673 if ($aWordFrequencyScores[$aSearchTerm['word_id']] < CONST_Max_Word_Frequency)
674 $aSearch['aName'][$aSearchTerm['word_id']] = $aSearchTerm['word_id'];
676 $aSearch['aNameNonSearch'][$aSearchTerm['word_id']] = $aSearchTerm['word_id'];
677 $aSearch['iNamePhrase'] = $iPhrase;
678 if ($aSearch['iSearchRank'] < $iMaxRank) $aNewWordsetSearches[] = $aSearch;
685 // Allow skipping a word - but at EXTREAM cost
686 //$aSearch = $aCurrentSearch;
687 //$aSearch['iSearchRank']+=100;
688 //$aNewWordsetSearches[] = $aSearch;
692 usort($aNewWordsetSearches, 'bySearchRank');
693 $aWordsetSearches = array_slice($aNewWordsetSearches, 0, 50);
695 //var_Dump('<hr>',sizeof($aWordsetSearches)); exit;
697 $aNewPhraseSearches = array_merge($aNewPhraseSearches, $aNewWordsetSearches);
698 usort($aNewPhraseSearches, 'bySearchRank');
700 $aSearchHash = array();
701 foreach($aNewPhraseSearches as $iSearch => $aSearch)
703 $sHash = serialize($aSearch);
704 if (isset($aSearchHash[$sHash])) unset($aNewPhraseSearches[$iSearch]);
705 else $aSearchHash[$sHash] = 1;
708 $aNewPhraseSearches = array_slice($aNewPhraseSearches, 0, 50);
711 // Re-group the searches by their score, junk anything over 20 as just not worth trying
712 $aGroupedSearches = array();
713 foreach($aNewPhraseSearches as $aSearch)
715 if ($aSearch['iSearchRank'] < $iMaxRank)
717 if (!isset($aGroupedSearches[$aSearch['iSearchRank']])) $aGroupedSearches[$aSearch['iSearchRank']] = array();
718 $aGroupedSearches[$aSearch['iSearchRank']][] = $aSearch;
721 ksort($aGroupedSearches);
724 $aSearches = array();
725 foreach($aGroupedSearches as $iScore => $aNewSearches)
727 $iSearchCount += sizeof($aNewSearches);
728 $aSearches = array_merge($aSearches, $aNewSearches);
729 if ($iSearchCount > 50) break;
732 //if (CONST_Debug) _debugDumpGroupedSearches($aGroupedSearches, $aValidTokens);
739 // Re-group the searches by their score, junk anything over 20 as just not worth trying
740 $aGroupedSearches = array();
741 foreach($aSearches as $aSearch)
743 if ($aSearch['iSearchRank'] < $iMaxRank)
745 if (!isset($aGroupedSearches[$aSearch['iSearchRank']])) $aGroupedSearches[$aSearch['iSearchRank']] = array();
746 $aGroupedSearches[$aSearch['iSearchRank']][] = $aSearch;
749 ksort($aGroupedSearches);
752 if (CONST_Debug) var_Dump($aGroupedSearches);
756 $aCopyGroupedSearches = $aGroupedSearches;
757 foreach($aCopyGroupedSearches as $iGroup => $aSearches)
759 foreach($aSearches as $iSearch => $aSearch)
761 if (sizeof($aSearch['aAddress']))
763 $iReverseItem = array_pop($aSearch['aAddress']);
764 if (isset($aPossibleMainWordIDs[$iReverseItem]))
766 $aSearch['aAddress'] = array_merge($aSearch['aAddress'], $aSearch['aName']);
767 $aSearch['aName'] = array($iReverseItem);
768 $aGroupedSearches[$iGroup][] = $aSearch;
770 //$aReverseSearch['aName'][$iReverseItem] = $iReverseItem;
771 //$aGroupedSearches[$iGroup][] = $aReverseSearch;
777 if (CONST_Search_TryDroppedAddressTerms && sizeof($aStructuredQuery) > 0)
779 $aCopyGroupedSearches = $aGroupedSearches;
780 foreach($aCopyGroupedSearches as $iGroup => $aSearches)
782 foreach($aSearches as $iSearch => $aSearch)
784 $aReductionsList = array($aSearch['aAddress']);
785 $iSearchRank = $aSearch['iSearchRank'];
786 while(sizeof($aReductionsList) > 0)
789 if ($iSearchRank > iMaxRank) break 3;
790 $aNewReductionsList = array();
791 foreach($aReductionsList as $aReductionsWordList)
793 for ($iReductionWord = 0; $iReductionWord < sizeof($aReductionsWordList); $iReductionWord++)
795 $aReductionsWordListResult = array_merge(array_slice($aReductionsWordList, 0, $iReductionWord), array_slice($aReductionsWordList, $iReductionWord+1));
796 $aReverseSearch = $aSearch;
797 $aSearch['aAddress'] = $aReductionsWordListResult;
798 $aSearch['iSearchRank'] = $iSearchRank;
799 $aGroupedSearches[$iSearchRank][] = $aReverseSearch;
800 if (sizeof($aReductionsWordListResult) > 0)
802 $aNewReductionsList[] = $aReductionsWordListResult;
806 $aReductionsList = $aNewReductionsList;
810 ksort($aGroupedSearches);
813 // Filter out duplicate searches
814 $aSearchHash = array();
815 foreach($aGroupedSearches as $iGroup => $aSearches)
817 foreach($aSearches as $iSearch => $aSearch)
819 $sHash = serialize($aSearch);
820 if (isset($aSearchHash[$sHash]))
822 unset($aGroupedSearches[$iGroup][$iSearch]);
823 if (sizeof($aGroupedSearches[$iGroup]) == 0) unset($aGroupedSearches[$iGroup]);
827 $aSearchHash[$sHash] = 1;
832 if (CONST_Debug) _debugDumpGroupedSearches($aGroupedSearches, $aValidTokens);
836 foreach($aGroupedSearches as $iGroupedRank => $aSearches)
839 foreach($aSearches as $aSearch)
843 if (CONST_Debug) { echo "<hr><b>Search Loop, group $iGroupLoop, loop $iQueryLoop</b>"; }
844 if (CONST_Debug) _debugDumpGroupedSearches(array($iGroupedRank => array($aSearch)), $aValidTokens);
847 // Must have a location term
848 if (!sizeof($aSearch['aName']) && !sizeof($aSearch['aAddress']) && !$aSearch['fLon'])
850 if ($aSearch['sCountryCode'] && !$aSearch['sClass'] && !$aSearch['sHouseNumber'])
852 if (4 >= $iMinAddressRank && 4 <= $iMaxAddressRank)
854 $sSQL = "select place_id from placex where calculated_country_code='".$aSearch['sCountryCode']."' and rank_search = 4";
855 if ($sCountryCodesSQL) $sSQL .= " and calculated_country_code in ($sCountryCodesSQL)";
856 $sSQL .= " order by st_area(geometry) desc limit 1";
857 if (CONST_Debug) var_dump($sSQL);
858 $aPlaceIDs = $oDB->getCol($sSQL);
863 if (!$bBoundingBoxSearch && !$aSearch['fLon']) continue;
864 if (!$aSearch['sClass']) continue;
865 $sSQL = "select count(*) from pg_tables where tablename = 'place_classtype_".$aSearch['sClass']."_".$aSearch['sType']."'";
866 if ($oDB->getOne($sSQL))
868 $sSQL = "select place_id from place_classtype_".$aSearch['sClass']."_".$aSearch['sType']." ct";
869 if ($sCountryCodesSQL) $sSQL .= " join placex using (place_id)";
870 $sSQL .= " where st_contains($sViewboxSmallSQL, ct.centroid)";
871 if ($sCountryCodesSQL) $sSQL .= " and calculated_country_code in ($sCountryCodesSQL)";
872 if (sizeof($aExcludePlaceIDs))
874 $sSQL .= " and place_id not in (".join(',',$aExcludePlaceIDs).")";
876 if ($sViewboxCentreSQL) $sSQL .= " order by st_distance($sViewboxCentreSQL, ct.centroid) asc";
877 $sSQL .= " limit $iLimit";
878 if (CONST_Debug) var_dump($sSQL);
879 $aPlaceIDs = $oDB->getCol($sSQL);
881 // If excluded place IDs are given, it is fair to assume that
882 // there have been results in the small box, so no further
883 // expansion in that case.
884 if (!sizeof($aPlaceIDs) && !sizeof($aExcludePlaceIDs))
886 $sSQL = "select place_id from place_classtype_".$aSearch['sClass']."_".$aSearch['sType']." ct";
887 if ($sCountryCodesSQL) $sSQL .= " join placex using (place_id)";
888 $sSQL .= " where st_contains($sViewboxLargeSQL, ct.centroid)";
889 if ($sCountryCodesSQL) $sSQL .= " and calculated_country_code in ($sCountryCodesSQL)";
890 if ($sViewboxCentreSQL) $sSQL .= " order by st_distance($sViewboxCentreSQL, ct.centroid) asc";
891 $sSQL .= " limit $iLimit";
892 if (CONST_Debug) var_dump($sSQL);
893 $aPlaceIDs = $oDB->getCol($sSQL);
898 $sSQL = "select place_id from placex where class='".$aSearch['sClass']."' and type='".$aSearch['sType']."'";
899 $sSQL .= " and st_contains($sViewboxSmallSQL, geometry) and linked_place_id is null";
900 if ($sCountryCodesSQL) $sSQL .= " and calculated_country_code in ($sCountryCodesSQL)";
901 if ($sViewboxCentreSQL) $sSQL .= " order by st_distance($sViewboxCentreSQL, centroid) asc";
902 $sSQL .= " limit $iLimit";
903 if (CONST_Debug) var_dump($sSQL);
904 $aPlaceIDs = $oDB->getCol($sSQL);
910 $aPlaceIDs = array();
912 // First we need a position, either aName or fLat or both
916 // TODO: filter out the pointless search terms (2 letter name tokens and less)
917 // they might be right - but they are just too darned expensive to run
918 if (sizeof($aSearch['aName'])) $aTerms[] = "name_vector @> ARRAY[".join($aSearch['aName'],",")."]";
919 if (sizeof($aSearch['aNameNonSearch'])) $aTerms[] = "array_cat(name_vector,ARRAY[]::integer[]) @> ARRAY[".join($aSearch['aNameNonSearch'],",")."]";
920 if (sizeof($aSearch['aAddress']) && $aSearch['aName'] != $aSearch['aAddress'])
922 // For infrequent name terms disable index usage for address
923 if (CONST_Search_NameOnlySearchFrequencyThreshold &&
924 sizeof($aSearch['aName']) == 1 &&
925 $aWordFrequencyScores[$aSearch['aName'][reset($aSearch['aName'])]] < CONST_Search_NameOnlySearchFrequencyThreshold)
927 $aTerms[] = "array_cat(nameaddress_vector,ARRAY[]::integer[]) @> ARRAY[".join(array_merge($aSearch['aAddress'],$aSearch['aAddressNonSearch']),",")."]";
931 $aTerms[] = "nameaddress_vector @> ARRAY[".join($aSearch['aAddress'],",")."]";
932 if (sizeof($aSearch['aAddressNonSearch'])) $aTerms[] = "array_cat(nameaddress_vector,ARRAY[]::integer[]) @> ARRAY[".join($aSearch['aAddressNonSearch'],",")."]";
935 if ($aSearch['sCountryCode']) $aTerms[] = "country_code = '".pg_escape_string($aSearch['sCountryCode'])."'";
936 if ($aSearch['sHouseNumber']) $aTerms[] = "address_rank between 16 and 27";
937 if ($aSearch['fLon'] && $aSearch['fLat'])
939 $aTerms[] = "ST_DWithin(centroid, ST_SetSRID(ST_Point(".$aSearch['fLon'].",".$aSearch['fLat']."),4326), ".$aSearch['fRadius'].")";
940 $aOrder[] = "ST_Distance(centroid, ST_SetSRID(ST_Point(".$aSearch['fLon'].",".$aSearch['fLat']."),4326)) ASC";
942 if (sizeof($aExcludePlaceIDs))
944 $aTerms[] = "place_id not in (".join(',',$aExcludePlaceIDs).")";
946 if ($sCountryCodesSQL)
948 $aTerms[] = "country_code in ($sCountryCodesSQL)";
951 if ($bBoundingBoxSearch) $aTerms[] = "centroid && $sViewboxSmallSQL";
952 if ($sNearPointSQL) $aOrder[] = "ST_Distance($sNearPointSQL, centroid) asc";
954 $sImportanceSQL = '(case when importance = 0 OR importance IS NULL then 0.75-(search_rank::float/40) else importance end)';
955 if ($sViewboxSmallSQL) $sImportanceSQL .= " * case when ST_Contains($sViewboxSmallSQL, centroid) THEN 1 ELSE 0.5 END";
956 if ($sViewboxLargeSQL) $sImportanceSQL .= " * case when ST_Contains($sViewboxLargeSQL, centroid) THEN 1 ELSE 0.5 END";
957 $aOrder[] = "$sImportanceSQL DESC";
958 if (sizeof($aSearch['aFullNameAddress']))
960 $aOrder[] = '(select count(*) from (select unnest(ARRAY['.join($aSearch['aFullNameAddress'],",").']) INTERSECT select unnest(nameaddress_vector))s) DESC';
965 $sSQL = "select place_id";
966 $sSQL .= " from search_name";
967 $sSQL .= " where ".join(' and ',$aTerms);
968 $sSQL .= " order by ".join(', ',$aOrder);
969 if ($aSearch['sHouseNumber'] || $aSearch['sClass'])
970 $sSQL .= " limit 50";
971 elseif (!sizeof($aSearch['aName']) && !sizeof($aSearch['aAddress']) && $aSearch['sClass'])
974 $sSQL .= " limit ".$iLimit;
976 if (CONST_Debug) { var_dump($sSQL); }
977 $aViewBoxPlaceIDs = $oDB->getAll($sSQL);
978 if (PEAR::IsError($aViewBoxPlaceIDs))
980 failInternalError("Could not get places for search terms.", $sSQL, $aViewBoxPlaceIDs);
982 //var_dump($aViewBoxPlaceIDs);
983 // Did we have an viewbox matches?
984 $aPlaceIDs = array();
985 $bViewBoxMatch = false;
986 foreach($aViewBoxPlaceIDs as $aViewBoxRow)
988 //if ($bViewBoxMatch == 1 && $aViewBoxRow['in_small'] == 'f') break;
989 //if ($bViewBoxMatch == 2 && $aViewBoxRow['in_large'] == 'f') break;
990 //if ($aViewBoxRow['in_small'] == 't') $bViewBoxMatch = 1;
991 //else if ($aViewBoxRow['in_large'] == 't') $bViewBoxMatch = 2;
992 $aPlaceIDs[] = $aViewBoxRow['place_id'];
995 //var_Dump($aPlaceIDs);
998 if ($aSearch['sHouseNumber'] && sizeof($aPlaceIDs))
1000 $aRoadPlaceIDs = $aPlaceIDs;
1001 $sPlaceIDs = join(',',$aPlaceIDs);
1003 // Now they are indexed look for a house attached to a street we found
1004 $sHouseNumberRegex = '\\\\m'.str_replace(' ','[-,/ ]',$aSearch['sHouseNumber']).'\\\\M';
1005 $sSQL = "select place_id from placex where parent_place_id in (".$sPlaceIDs.") and housenumber ~* E'".$sHouseNumberRegex."'";
1006 if (sizeof($aExcludePlaceIDs))
1008 $sSQL .= " and place_id not in (".join(',',$aExcludePlaceIDs).")";
1010 $sSQL .= " limit $iLimit";
1011 if (CONST_Debug) var_dump($sSQL);
1012 $aPlaceIDs = $oDB->getCol($sSQL);
1014 // If not try the aux fallback table
1015 if (!sizeof($aPlaceIDs))
1017 $sSQL = "select place_id from location_property_aux where parent_place_id in (".$sPlaceIDs.") and housenumber = '".pg_escape_string($aSearch['sHouseNumber'])."'";
1018 if (sizeof($aExcludePlaceIDs))
1020 $sSQL .= " and place_id not in (".join(',',$aExcludePlaceIDs).")";
1022 //$sSQL .= " limit $iLimit";
1023 if (CONST_Debug) var_dump($sSQL);
1024 $aPlaceIDs = $oDB->getCol($sSQL);
1027 if (!sizeof($aPlaceIDs))
1029 $sSQL = "select place_id from location_property_tiger where parent_place_id in (".$sPlaceIDs.") and housenumber = '".pg_escape_string($aSearch['sHouseNumber'])."'";
1030 if (sizeof($aExcludePlaceIDs))
1032 $sSQL .= " and place_id not in (".join(',',$aExcludePlaceIDs).")";
1034 //$sSQL .= " limit $iLimit";
1035 if (CONST_Debug) var_dump($sSQL);
1036 $aPlaceIDs = $oDB->getCol($sSQL);
1039 // Fallback to the road
1040 if (!sizeof($aPlaceIDs) && preg_match('/[0-9]+/', $aSearch['sHouseNumber']))
1042 $aPlaceIDs = $aRoadPlaceIDs;
1047 if ($aSearch['sClass'] && sizeof($aPlaceIDs))
1049 $sPlaceIDs = join(',',$aPlaceIDs);
1050 $aClassPlaceIDs = array();
1052 if (!$aSearch['sOperator'] || $aSearch['sOperator'] == 'name')
1054 // If they were searching for a named class (i.e. 'Kings Head pub') then we might have an extra match
1055 $sSQL = "select place_id from placex where place_id in ($sPlaceIDs) and class='".$aSearch['sClass']."' and type='".$aSearch['sType']."'";
1056 $sSQL .= " and linked_place_id is null";
1057 if ($sCountryCodesSQL) $sSQL .= " and calculated_country_code in ($sCountryCodesSQL)";
1058 $sSQL .= " order by rank_search asc limit $iLimit";
1059 if (CONST_Debug) var_dump($sSQL);
1060 $aClassPlaceIDs = $oDB->getCol($sSQL);
1063 if (!$aSearch['sOperator'] || $aSearch['sOperator'] == 'near') // & in
1065 $sSQL = "select count(*) from pg_tables where tablename = 'place_classtype_".$aSearch['sClass']."_".$aSearch['sType']."'";
1066 $bCacheTable = $oDB->getOne($sSQL);
1068 $sSQL = "select min(rank_search) from placex where place_id in ($sPlaceIDs)";
1070 if (CONST_Debug) var_dump($sSQL);
1071 $iMaxRank = ((int)$oDB->getOne($sSQL));
1073 // For state / country level searches the normal radius search doesn't work very well
1074 $sPlaceGeom = false;
1075 if ($iMaxRank < 9 && $bCacheTable)
1077 // Try and get a polygon to search in instead
1078 $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";
1079 if (CONST_Debug) var_dump($sSQL);
1080 $sPlaceGeom = $oDB->getOne($sSQL);
1090 $sSQL = "select place_id from placex where place_id in ($sPlaceIDs) and rank_search < $iMaxRank";
1091 if (CONST_Debug) var_dump($sSQL);
1092 $aPlaceIDs = $oDB->getCol($sSQL);
1093 $sPlaceIDs = join(',',$aPlaceIDs);
1096 if ($sPlaceIDs || $sPlaceGeom)
1102 // More efficient - can make the range bigger
1106 if ($sNearPointSQL) $sOrderBySQL = "ST_Distance($sNearPointSQL, l.centroid)";
1107 else if ($sPlaceIDs) $sOrderBySQL = "ST_Distance(l.centroid, f.geometry)";
1108 else if ($sPlaceGeom) $sOrderBysSQL = "ST_Distance(st_centroid('".$sPlaceGeom."'), l.centroid)";
1110 $sSQL = "select distinct l.place_id".($sOrderBySQL?','.$sOrderBySQL:'')." from place_classtype_".$aSearch['sClass']."_".$aSearch['sType']." as l";
1111 if ($sCountryCodesSQL) $sSQL .= " join placex as lp using (place_id)";
1114 $sSQL .= ",placex as f where ";
1115 $sSQL .= "f.place_id in ($sPlaceIDs) and ST_DWithin(l.centroid, f.centroid, $fRange) ";
1120 $sSQL .= "ST_Contains('".$sPlaceGeom."', l.centroid) ";
1122 if (sizeof($aExcludePlaceIDs))
1124 $sSQL .= " and l.place_id not in (".join(',',$aExcludePlaceIDs).")";
1126 if ($sCountryCodesSQL) $sSQL .= " and lp.calculated_country_code in ($sCountryCodesSQL)";
1127 if ($sOrderBySQL) $sSQL .= "order by ".$sOrderBySQL." asc";
1128 if ($iOffset) $sSQL .= " offset $iOffset";
1129 $sSQL .= " limit $iLimit";
1130 if (CONST_Debug) var_dump($sSQL);
1131 $aClassPlaceIDs = array_merge($aClassPlaceIDs, $oDB->getCol($sSQL));
1135 if (isset($aSearch['fRadius']) && $aSearch['fRadius']) $fRange = $aSearch['fRadius'];
1138 if ($sNearPointSQL) $sOrderBySQL = "ST_Distance($sNearPointSQL, l.geometry)";
1139 else $sOrderBySQL = "ST_Distance(l.geometry, f.geometry)";
1141 $sSQL = "select distinct l.place_id".($sOrderBysSQL?','.$sOrderBysSQL:'')." from placex as l,placex as f where ";
1142 $sSQL .= "f.place_id in ( $sPlaceIDs) and ST_DWithin(l.geometry, f.centroid, $fRange) ";
1143 $sSQL .= "and l.class='".$aSearch['sClass']."' and l.type='".$aSearch['sType']."' ";
1144 if (sizeof($aExcludePlaceIDs))
1146 $sSQL .= " and l.place_id not in (".join(',',$aExcludePlaceIDs).")";
1148 if ($sCountryCodesSQL) $sSQL .= " and l.calculated_country_code in ($sCountryCodesSQL)";
1149 if ($sOrderBy) $sSQL .= "order by ".$OrderBysSQL." 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));
1158 $aPlaceIDs = $aClassPlaceIDs;
1164 if (PEAR::IsError($aPlaceIDs))
1166 failInternalError("Could not get place IDs from tokens." ,$sSQL, $aPlaceIDs);
1169 if (CONST_Debug) { echo "<br><b>Place IDs:</b> "; var_Dump($aPlaceIDs); }
1171 foreach($aPlaceIDs as $iPlaceID)
1173 $aResultPlaceIDs[$iPlaceID] = $iPlaceID;
1175 if ($iQueryLoop > 20) break;
1178 if (isset($aResultPlaceIDs) && sizeof($aResultPlaceIDs) && ($iMinAddressRank != 0 || $iMaxAddressRank != 30))
1180 // Need to verify passes rank limits before dropping out of the loop (yuk!)
1181 $sSQL = "select place_id from placex where place_id in (".join(',',$aResultPlaceIDs).") ";
1182 $sSQL .= "and (placex.rank_address between $iMinAddressRank and $iMaxAddressRank ";
1183 if (14 >= $iMinAddressRank && 14 <= $iMaxAddressRank) $sSQL .= " OR (extratags->'place') = 'city'";
1184 if ($aAddressRankList) $sSQL .= " OR placex.rank_address in (".join(',',$aAddressRankList).")";
1185 $sSQL .= ") UNION select place_id from location_property_tiger where place_id in (".join(',',$aResultPlaceIDs).") ";
1186 $sSQL .= "and (30 between $iMinAddressRank and $iMaxAddressRank ";
1187 if ($aAddressRankList) $sSQL .= " OR 30 in (".join(',',$aAddressRankList).")";
1189 if (CONST_Debug) var_dump($sSQL);
1190 $aResultPlaceIDs = $oDB->getCol($sSQL);
1195 if (isset($aResultPlaceIDs) && sizeof($aResultPlaceIDs)) break;
1196 if ($iGroupLoop > 4) break;
1197 if ($iQueryLoop > 30) break;
1200 // Did we find anything?
1201 if (isset($aResultPlaceIDs) && sizeof($aResultPlaceIDs))
1203 //var_Dump($aResultPlaceIDs);exit;
1204 // Get the details for display (is this a redundant extra step?)
1205 $sPlaceIDs = join(',',$aResultPlaceIDs);
1206 $sOrderSQL = 'CASE ';
1207 foreach(array_keys($aResultPlaceIDs) as $iOrder => $iPlaceID)
1209 $sOrderSQL .= 'when min(place_id) = '.$iPlaceID.' then '.$iOrder.' ';
1211 $sOrderSQL .= ' ELSE 10000000 END';
1212 $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,";
1213 $sSQL .= "get_address_by_language(place_id, $sLanguagePrefArraySQL) as langaddress,";
1214 $sSQL .= "get_name_by_language(name, $sLanguagePrefArraySQL) as placename,";
1215 $sSQL .= "get_name_by_language(name, ARRAY['ref']) as ref,";
1216 $sSQL .= "avg(ST_X(centroid)) as lon,avg(ST_Y(centroid)) as lat, ";
1217 //$sSQL .= $sOrderSQL." as porder, ";
1218 $sSQL .= "coalesce(importance,0.75-(rank_search::float/40)) as importance, ";
1219 $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, ";
1220 $sSQL .= "(extratags->'place') as extra_place ";
1221 $sSQL .= "from placex where place_id in ($sPlaceIDs) ";
1222 $sSQL .= "and (placex.rank_address between $iMinAddressRank and $iMaxAddressRank ";
1223 if (14 >= $iMinAddressRank && 14 <= $iMaxAddressRank) $sSQL .= " OR (extratags->'place') = 'city'";
1224 if ($aAddressRankList) $sSQL .= " OR placex.rank_address in (".join(',',$aAddressRankList).")";
1226 if ($sAllowedTypesSQLList) $sSQL .= "and placex.class in $sAllowedTypesSQLList ";
1227 $sSQL .= "and linked_place_id is null ";
1228 $sSQL .= "group by osm_type,osm_id,class,type,admin_level,rank_search,rank_address,calculated_country_code,importance";
1229 if (!$bDeDupe) $sSQL .= ",place_id";
1230 $sSQL .= ",langaddress ";
1231 $sSQL .= ",placename ";
1233 $sSQL .= ",extratags->'place' ";
1235 $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,";
1236 $sSQL .= "get_address_by_language(place_id, $sLanguagePrefArraySQL) as langaddress,";
1237 $sSQL .= "null as placename,";
1238 $sSQL .= "null as ref,";
1239 $sSQL .= "avg(ST_X(centroid)) as lon,avg(ST_Y(centroid)) as lat, ";
1240 //$sSQL .= $sOrderSQL." as porder, ";
1241 $sSQL .= "-0.15 as importance, ";
1242 $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, ";
1243 $sSQL .= "null as extra_place ";
1244 $sSQL .= "from location_property_tiger where place_id in ($sPlaceIDs) ";
1245 $sSQL .= "and 30 between $iMinAddressRank and $iMaxAddressRank ";
1246 $sSQL .= "group by place_id";
1247 if (!$bDeDupe) $sSQL .= ",place_id";
1249 $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,";
1250 $sSQL .= "get_address_by_language(place_id, $sLanguagePrefArraySQL) as langaddress,";
1251 $sSQL .= "null as placename,";
1252 $sSQL .= "null as ref,";
1253 $sSQL .= "avg(ST_X(centroid)) as lon,avg(ST_Y(centroid)) as lat, ";
1254 //$sSQL .= $sOrderSQL." as porder, ";
1255 $sSQL .= "-0.10 as importance, ";
1256 $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, ";
1257 $sSQL .= "null as extra_place ";
1258 $sSQL .= "from location_property_aux where place_id in ($sPlaceIDs) ";
1259 $sSQL .= "and 30 between $iMinAddressRank and $iMaxAddressRank ";
1260 $sSQL .= "group by place_id";
1261 if (!$bDeDupe) $sSQL .= ",place_id";
1262 $sSQL .= ",get_address_by_language(place_id, $sLanguagePrefArraySQL) ";
1263 $sSQL .= "order by importance desc";
1264 //$sSQL .= "order by rank_search,rank_address,porder asc";
1265 if (CONST_Debug) { echo "<hr>"; var_dump($sSQL); }
1266 $aSearchResults = $oDB->getAll($sSQL);
1267 //var_dump($sSQL,$aSearchResults);exit;
1269 if (PEAR::IsError($aSearchResults))
1271 failInternalError("Could not get details for place.", $sSQL, $aSearchResults);
1274 } // end if ($sQuery)
1277 if (isset($_GET['nearlat']) && trim($_GET['nearlat'])!=='' && isset($_GET['nearlon']) && trim($_GET['nearlon']) !== '')
1279 $iPlaceID = geocodeReverse((float)$_GET['nearlat'], (float)$_GET['nearlon']);
1283 $aResultPlaceIDs = array($iPlaceID);
1284 // TODO: this needs refactoring!
1286 // Get the details for display (is this a redundant extra step?)
1287 $sPlaceIDs = join(',',$aResultPlaceIDs);
1288 $sOrderSQL = 'CASE ';
1289 foreach(array_keys($aResultPlaceIDs) as $iOrder => $iPlaceID)
1291 $sOrderSQL .= 'when min(place_id) = '.$iPlaceID.' then '.$iOrder.' ';
1293 $sOrderSQL .= ' ELSE 10000000 END';
1294 $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,";
1295 $sSQL .= "get_address_by_language(place_id, $sLanguagePrefArraySQL) as langaddress,";
1296 $sSQL .= "get_name_by_language(name, $sLanguagePrefArraySQL) as placename,";
1297 $sSQL .= "get_name_by_language(name, ARRAY['ref']) as ref,";
1298 $sSQL .= "avg(ST_X(centroid)) as lon,avg(ST_Y(centroid)) as lat, ";
1299 //$sSQL .= $sOrderSQL." as porder, ";
1300 $sSQL .= "coalesce(importance,0.75-(rank_search::float/40)) as importance, ";
1301 $sSQL .= "(extratags->'place') as extra_place ";
1302 $sSQL .= "from placex where place_id in ($sPlaceIDs) ";
1303 $sSQL .= "and (placex.rank_address between $iMinAddressRank and $iMaxAddressRank ";
1304 if (14 >= $iMinAddressRank && 14 <= $iMaxAddressRank) $sSQL .= " OR (extratags->'place') = 'city'";
1305 if ($aAddressRankList) $sSQL .= " OR placex.rank_address in (".join(',',$aAddressRankList).")";
1307 $sSQL .= "group by osm_type,osm_id,class,type,admin_level,rank_search,rank_address,calculated_country_code,importance";
1308 if (!$bDeDupe) $sSQL .= ",place_id";
1309 $sSQL .= ",get_address_by_language(place_id, $sLanguagePrefArraySQL) ";
1310 $sSQL .= ",get_name_by_language(name, $sLanguagePrefArraySQL) ";
1311 $sSQL .= ",get_name_by_language(name, ARRAY['ref']) ";
1312 $sSQL .= ",extratags->'place' ";
1314 $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,";
1315 $sSQL .= "get_address_by_language(place_id, $sLanguagePrefArraySQL) as langaddress,";
1316 $sSQL .= "null as placename,";
1317 $sSQL .= "null as ref,";
1318 $sSQL .= "avg(ST_X(centroid)) as lon,avg(ST_Y(centroid)) as lat, ";
1319 //$sSQL .= $sOrderSQL." as porder, ";
1320 $sSQL .= "-0.15 as importance, ";
1321 $sSQL .= "null as extra_place ";
1322 $sSQL .= "from location_property_tiger where place_id in ($sPlaceIDs) ";
1323 $sSQL .= "and 30 between $iMinAddressRank and $iMaxAddressRank ";
1324 $sSQL .= "group by place_id";
1325 if (!$bDeDupe) $sSQL .= ",place_id";
1327 $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,";
1328 $sSQL .= "get_address_by_language(place_id, $sLanguagePrefArraySQL) as langaddress,";
1329 $sSQL .= "null as placename,";
1330 $sSQL .= "null as ref,";
1331 $sSQL .= "avg(ST_X(centroid)) as lon,avg(ST_Y(centroid)) as lat, ";
1332 //$sSQL .= $sOrderSQL." as porder, ";
1333 $sSQL .= "-0.10 as importance, ";
1334 $sSQL .= "null as extra_place ";
1335 $sSQL .= "from location_property_aux where place_id in ($sPlaceIDs) ";
1336 $sSQL .= "and 30 between $iMinAddressRank and $iMaxAddressRank ";
1337 $sSQL .= "group by place_id";
1338 if (!$bDeDupe) $sSQL .= ",place_id";
1339 $sSQL .= ",get_address_by_language(place_id, $sLanguagePrefArraySQL) ";
1340 $sSQL .= "order by importance desc";
1341 //$sSQL .= "order by rank_search,rank_address,porder asc";
1342 if (CONST_Debug) { echo "<hr>", var_dump($sSQL); }
1343 $aSearchResults = $oDB->getAll($sSQL);
1344 //var_dump($sSQL,$aSearchResults);exit;
1346 if (PEAR::IsError($aSearchResults))
1348 failInternalError("Could not get details for place (near).", $sSQL, $aSearchResults);
1353 $aSearchResults = array();
1359 $sSearchResult = '';
1360 if (!sizeof($aSearchResults) && isset($_GET['q']) && $_GET['q'])
1362 $sSearchResult = 'No Results Found';
1364 //var_Dump($aSearchResults);
1366 $aClassType = getClassTypesWithImportance();
1367 $aRecheckWords = preg_split('/\b/u',$sQuery);
1368 foreach($aRecheckWords as $i => $sWord)
1370 if (!$sWord) unset($aRecheckWords[$i]);
1372 foreach($aSearchResults as $iResNum => $aResult)
1374 if (CONST_Search_AreaPolygons)
1376 // Get the bounding box and outline polygon
1377 $sSQL = "select place_id,numfeatures,area,outline,";
1378 $sSQL .= "ST_Y(ST_PointN(ST_ExteriorRing(Box2D(outline)),4)) as minlat,ST_Y(ST_PointN(ST_ExteriorRing(Box2D(outline)),2)) as maxlat,";
1379 $sSQL .= "ST_X(ST_PointN(ST_ExteriorRing(Box2D(outline)),1)) as minlon,ST_X(ST_PointN(ST_ExteriorRing(Box2D(outline)),3)) as maxlon,";
1380 $sSQL .= "ST_AsText(outline) as outlinestring from get_place_boundingbox_quick(".$aResult['place_id'].")";
1382 $sSQL = "select place_id,0 as numfeatures,st_area(geometry) as area,";
1383 $sSQL .= "ST_Y(centroid) as centrelat,ST_X(centroid) as centrelon,";
1384 $sSQL .= "ST_Y(ST_PointN(ST_ExteriorRing(Box2D(geometry)),4)) as minlat,ST_Y(ST_PointN(ST_ExteriorRing(Box2D(geometry)),2)) as maxlat,";
1385 $sSQL .= "ST_X(ST_PointN(ST_ExteriorRing(Box2D(geometry)),1)) as minlon,ST_X(ST_PointN(ST_ExteriorRing(Box2D(geometry)),3)) as maxlon";
1386 if ($bAsGeoJSON) $sSQL .= ",ST_AsGeoJSON(geometry) as asgeojson";
1387 if ($bAsKML) $sSQL .= ",ST_AsKML(geometry) as askml";
1388 if ($bAsSVG) $sSQL .= ",ST_AsSVG(geometry) as assvg";
1389 if ($bAsText || $bShowPolygons) $sSQL .= ",ST_AsText(geometry) as astext";
1390 $sSQL .= " from placex where place_id = ".$aResult['place_id'].' and st_geometrytype(Box2D(geometry)) = \'ST_Polygon\'';
1391 $aPointPolygon = $oDB->getRow($sSQL);
1392 if (PEAR::IsError($aPointPolygon))
1394 failInternalError("Could not get outline.", $sSQL, $aPointPolygon);
1396 if ($aPointPolygon['place_id'])
1398 if ($bAsGeoJSON) $aResult['asgeojson'] = $aPointPolygon['asgeojson'];
1399 if ($bAsKML) $aResult['askml'] = $aPointPolygon['askml'];
1400 if ($bAsSVG) $aResult['assvg'] = $aPointPolygon['assvg'];
1401 if ($bAsText) $aResult['astext'] = $aPointPolygon['astext'];
1403 if ($aPointPolygon['centrelon'] !== null && $aPointPolygon['centrelat'] !== null )
1405 $aResult['lat'] = $aPointPolygon['centrelat'];
1406 $aResult['lon'] = $aPointPolygon['centrelon'];
1410 // Translate geometary string to point array
1411 if (preg_match('#POLYGON\\(\\(([- 0-9.,]+)#',$aPointPolygon['astext'],$aMatch))
1413 preg_match_all('/(-?[0-9.]+) (-?[0-9.]+)/',$aMatch[1],$aPolyPoints,PREG_SET_ORDER);
1415 elseif (preg_match('#MULTIPOLYGON\\(\\(\\(([- 0-9.,]+)#',$aPointPolygon['astext'],$aMatch))
1417 preg_match_all('/(-?[0-9.]+) (-?[0-9.]+)/',$aMatch[1],$aPolyPoints,PREG_SET_ORDER);
1419 elseif (preg_match('#POINT\\((-?[0-9.]+) (-?[0-9.]+)\\)#',$aPointPolygon['astext'],$aMatch))
1422 $iSteps = ($fRadius * 40000)^2;
1423 $fStepSize = (2*pi())/$iSteps;
1424 $aPolyPoints = array();
1425 for($f = 0; $f < 2*pi(); $f += $fStepSize)
1427 $aPolyPoints[] = array('',$aMatch[1]+($fRadius*sin($f)),$aMatch[2]+($fRadius*cos($f)));
1429 $aPointPolygon['minlat'] = $aPointPolygon['minlat'] - $fRadius;
1430 $aPointPolygon['maxlat'] = $aPointPolygon['maxlat'] + $fRadius;
1431 $aPointPolygon['minlon'] = $aPointPolygon['minlon'] - $fRadius;
1432 $aPointPolygon['maxlon'] = $aPointPolygon['maxlon'] + $fRadius;
1436 // Output data suitable for display (points and a bounding box)
1437 if ($bShowPolygons && isset($aPolyPoints))
1439 $aResult['aPolyPoints'] = array();
1440 foreach($aPolyPoints as $aPoint)
1442 $aResult['aPolyPoints'][] = array($aPoint[1], $aPoint[2]);
1445 $aResult['aBoundingBox'] = array($aPointPolygon['minlat'],$aPointPolygon['maxlat'],$aPointPolygon['minlon'],$aPointPolygon['maxlon']);
1449 if ($aResult['extra_place'] == 'city')
1451 $aResult['class'] = 'place';
1452 $aResult['type'] = 'city';
1453 $aResult['rank_search'] = 16;
1456 if (!isset($aResult['aBoundingBox']))
1459 $fDiameter = 0.0001;
1461 if (isset($aClassType[$aResult['class'].':'.$aResult['type'].':'.$aResult['admin_level']]['defdiameter'])
1462 && $aClassType[$aResult['class'].':'.$aResult['type'].':'.$aResult['admin_level']]['defdiameter'])
1464 $fDiameter = $aClassType[$aResult['class'].':'.$aResult['type'].':'.$aResult['admin_level']]['defzoom'];
1466 elseif (isset($aClassType[$aResult['class'].':'.$aResult['type']]['defdiameter'])
1467 && $aClassType[$aResult['class'].':'.$aResult['type']]['defdiameter'])
1469 $fDiameter = $aClassType[$aResult['class'].':'.$aResult['type']]['defdiameter'];
1471 $fRadius = $fDiameter / 2;
1473 $iSteps = max(8,min(100,$fRadius * 3.14 * 100000));
1474 $fStepSize = (2*pi())/$iSteps;
1475 $aPolyPoints = array();
1476 for($f = 0; $f < 2*pi(); $f += $fStepSize)
1478 $aPolyPoints[] = array('',$aResult['lon']+($fRadius*sin($f)),$aResult['lat']+($fRadius*cos($f)));
1480 $aPointPolygon['minlat'] = $aResult['lat'] - $fRadius;
1481 $aPointPolygon['maxlat'] = $aResult['lat'] + $fRadius;
1482 $aPointPolygon['minlon'] = $aResult['lon'] - $fRadius;
1483 $aPointPolygon['maxlon'] = $aResult['lon'] + $fRadius;
1485 // Output data suitable for display (points and a bounding box)
1488 $aResult['aPolyPoints'] = array();
1489 foreach($aPolyPoints as $aPoint)
1491 $aResult['aPolyPoints'][] = array($aPoint[1], $aPoint[2]);
1494 $aResult['aBoundingBox'] = array($aPointPolygon['minlat'],$aPointPolygon['maxlat'],$aPointPolygon['minlon'],$aPointPolygon['maxlon']);
1497 // Is there an icon set for this type of result?
1498 if (isset($aClassType[$aResult['class'].':'.$aResult['type']]['icon'])
1499 && $aClassType[$aResult['class'].':'.$aResult['type']]['icon'])
1501 $aResult['icon'] = CONST_Website_BaseURL.'images/mapicons/'.$aClassType[$aResult['class'].':'.$aResult['type']]['icon'].'.p.20.png';
1504 if (isset($aClassType[$aResult['class'].':'.$aResult['type']]['label'])
1505 && $aClassType[$aResult['class'].':'.$aResult['type']]['label'])
1507 $aResult['label'] = $aClassType[$aResult['class'].':'.$aResult['type']]['label'];
1510 if ($bShowAddressDetails)
1512 $aResult['address'] = getAddressDetails($oDB, $sLanguagePrefArraySQL, $aResult['place_id'], $aResult['country_code']);
1513 if ($aResult['extra_place'] == 'city' && !isset($aResult['address']['city']))
1515 $aResult['address'] = array_merge(array('city' => array_shift(array_values($aResult['address']))), $aResult['address']);
1518 //var_dump($aResult['address']);
1522 // Adjust importance for the number of exact string matches in the result
1523 $aResult['importance'] = max(0.001,$aResult['importance']);
1525 $sAddress = $aResult['langaddress'];
1526 foreach($aRecheckWords as $i => $sWord)
1528 if (stripos($sAddress, $sWord)!==false) $iCountWords++;
1531 $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
1533 //if (CONST_Debug) var_dump($aResult['class'].':'.$aResult['type'].':'.$aResult['admin_level']);
1535 if (isset($aClassType[$aResult['class'].':'.$aResult['type'].':'.$aResult['admin_level']]['importance'])
1536 && $aClassType[$aResult['class'].':'.$aResult['type'].':'.$aResult['admin_level']]['importance'])
1538 $aResult['importance'] = $aClassType[$aResult['class'].':'.$aResult['type'].':'.$aResult['admin_level']]['importance'];
1540 elseif (isset($aClassType[$aResult['class'].':'.$aResult['type']]['importance'])
1541 && $aClassType[$aResult['class'].':'.$aResult['type']]['importance'])
1543 $aResult['importance'] = $aClassType[$aResult['class'].':'.$aResult['type']]['importance'];
1547 $aResult['importance'] = 1000000000000000;
1550 $aResult['name'] = $aResult['langaddress'];
1551 $aResult['foundorder'] = -$aResult['addressimportance'];
1552 $aSearchResults[$iResNum] = $aResult;
1554 uasort($aSearchResults, 'byImportance');
1556 $aOSMIDDone = array();
1557 $aClassTypeNameDone = array();
1558 $aToFilter = $aSearchResults;
1559 $aSearchResults = array();
1562 foreach($aToFilter as $iResNum => $aResult)
1564 if ($aResult['type'] == 'adminitrative') $aResult['type'] = 'administrative';
1565 $aExcludePlaceIDs[$aResult['place_id']] = $aResult['place_id'];
1568 $fLat = $aResult['lat'];
1569 $fLon = $aResult['lon'];
1570 if (isset($aResult['zoom'])) $iZoom = $aResult['zoom'];
1573 if (!$bDeDupe || (!isset($aOSMIDDone[$aResult['osm_type'].$aResult['osm_id']])
1574 && !isset($aClassTypeNameDone[$aResult['osm_type'].$aResult['class'].$aResult['type'].$aResult['name'].$aResult['admin_level']])))
1576 $aOSMIDDone[$aResult['osm_type'].$aResult['osm_id']] = true;
1577 $aClassTypeNameDone[$aResult['osm_type'].$aResult['class'].$aResult['type'].$aResult['name'].$aResult['admin_level']] = true;
1578 $aSearchResults[] = $aResult;
1581 // Absolute limit on number of results
1582 if (sizeof($aSearchResults) >= $iFinalLimit) break;
1585 $sDataDate = $oDB->getOne("select TO_CHAR(lastimportdate - '2 minutes'::interval,'YYYY/MM/DD HH24:MI')||' GMT' from import_status limit 1");
1587 if (isset($_GET['nearlat']) && isset($_GET['nearlon']))
1589 $sQuery .= ' ['.$_GET['nearlat'].','.$_GET['nearlon'].']';
1594 logEnd($oDB, $hLog, sizeof($aToFilter));
1596 $sMoreURL = CONST_Website_BaseURL.'search?format='.urlencode($sOutputFormat).'&exclude_place_ids='.join(',',$aExcludePlaceIDs);
1597 if (isset($_SERVER["HTTP_ACCEPT_LANGUAGE"])) $sMoreURL .= '&accept-language='.$_SERVER["HTTP_ACCEPT_LANGUAGE"];
1598 if ($bShowPolygons) $sMoreURL .= '&polygon=1';
1599 if ($bShowAddressDetails) $sMoreURL .= '&addressdetails=1';
1600 if (isset($_GET['viewbox']) && $_GET['viewbox']) $sMoreURL .= '&viewbox='.urlencode($_GET['viewbox']);
1601 if (isset($_GET['nearlat']) && isset($_GET['nearlon'])) $sMoreURL .= '&nearlat='.(float)$_GET['nearlat'].'&nearlon='.(float)$_GET['nearlon'];
1602 $sMoreURL .= '&q='.urlencode($sQuery);
1604 if (CONST_Debug) exit;
1606 include(CONST_BasePath.'/lib/template/search-'.$sOutputFormat.'.php');