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;
450 // US ZIP+4 codes - if there is no token,
451 // merge in the 5-digit ZIP code
452 else if (!isset($aValidTokens[$sToken]) && preg_match('/^([0-9]{5}) [0-9]{4}$/', $sToken, $aData))
454 if (isset($aValidTokens[$aData[1]]))
456 foreach($aValidTokens[$aData[1]] as $aToken)
458 if (!$aToken['class'])
460 if (isset($aValidTokens[$sToken]))
462 $aValidTokens[$sToken][] = $aToken;
466 $aValidTokens[$sToken] = array($aToken);
474 foreach($aTokens as $sToken)
476 // Unknown single word token with a number - assume it is a house number
477 if (!isset($aValidTokens[' '.$sToken]) && strpos($sToken,' ') === false && preg_match('/[0-9]/', $sToken))
479 $aValidTokens[' '.$sToken] = array(array('class'=>'place','type'=>'house'));
483 // Any words that have failed completely?
486 // Start the search process
487 $aResultPlaceIDs = array();
490 Calculate all searches using aValidTokens i.e.
491 'Wodsworth Road, Sheffield' =>
495 0 1 (wodsworth)(road)
498 Score how good the search is so they can be ordered
500 foreach($aPhrases as $iPhrase => $sPhrase)
502 $aNewPhraseSearches = array();
503 if ($bStructuredPhrases) $sPhraseType = $aPhraseTypes[$iPhrase];
504 else $sPhraseType = '';
506 foreach($aPhrases[$iPhrase]['wordsets'] as $aWordset)
508 $aWordsetSearches = $aSearches;
510 // Add all words from this wordset
511 foreach($aWordset as $iToken => $sToken)
513 //echo "<br><b>$sToken</b>";
514 $aNewWordsetSearches = array();
516 foreach($aWordsetSearches as $aCurrentSearch)
519 //var_dump($aCurrentSearch);
522 // If the token is valid
523 if (isset($aValidTokens[' '.$sToken]))
525 foreach($aValidTokens[' '.$sToken] as $aSearchTerm)
527 $aSearch = $aCurrentSearch;
528 $aSearch['iSearchRank']++;
529 if (($sPhraseType == '' || $sPhraseType == 'country') && !empty($aSearchTerm['country_code']) && $aSearchTerm['country_code'] != '0')
531 if ($aSearch['sCountryCode'] === false)
533 $aSearch['sCountryCode'] = strtolower($aSearchTerm['country_code']);
534 // Country is almost always at the end of the string - increase score for finding it anywhere else (optimisation)
535 // If reverse order is enabled, it may appear at the beginning as well.
536 if (($iToken+1 != sizeof($aWordset) || $iPhrase+1 != sizeof($aPhrases)) &&
537 (!$bReverseInPlan || $iToken > 0 || $iPhrase > 0))
539 $aSearch['iSearchRank'] += 5;
541 if ($aSearch['iSearchRank'] < $iMaxRank) $aNewWordsetSearches[] = $aSearch;
544 elseif (isset($aSearchTerm['lat']) && $aSearchTerm['lat'] !== '' && $aSearchTerm['lat'] !== null)
546 if ($aSearch['fLat'] === '')
548 $aSearch['fLat'] = $aSearchTerm['lat'];
549 $aSearch['fLon'] = $aSearchTerm['lon'];
550 $aSearch['fRadius'] = $aSearchTerm['radius'];
551 if ($aSearch['iSearchRank'] < $iMaxRank) $aNewWordsetSearches[] = $aSearch;
554 elseif ($sPhraseType == 'postalcode')
556 // We need to try the case where the postal code is the primary element (i.e. no way to tell if it is (postalcode, city) OR (city, postalcode) so try both
557 if (isset($aSearchTerm['word_id']) && $aSearchTerm['word_id'])
559 // If we already have a name try putting the postcode first
560 if (sizeof($aSearch['aName']))
562 $aNewSearch = $aSearch;
563 $aNewSearch['aAddress'] = array_merge($aNewSearch['aAddress'], $aNewSearch['aName']);
564 $aNewSearch['aName'] = array();
565 $aNewSearch['aName'][$aSearchTerm['word_id']] = $aSearchTerm['word_id'];
566 if ($aSearch['iSearchRank'] < $iMaxRank) $aNewWordsetSearches[] = $aNewSearch;
569 if (sizeof($aSearch['aName']))
571 if ((!$bStructuredPhrases || $iPhrase > 0) && $sPhraseType != 'country' && (!isset($aValidTokens[$sToken]) || strlen($sToken) < 4 || strpos($sToken, ' ') !== false))
573 $aSearch['aAddress'][$aSearchTerm['word_id']] = $aSearchTerm['word_id'];
577 $aCurrentSearch['aFullNameAddress'][$aSearchTerm['word_id']] = $aSearchTerm['word_id'];
578 $aSearch['iSearchRank'] += 1000; // skip;
583 $aSearch['aName'][$aSearchTerm['word_id']] = $aSearchTerm['word_id'];
584 //$aSearch['iNamePhrase'] = $iPhrase;
586 if ($aSearch['iSearchRank'] < $iMaxRank) $aNewWordsetSearches[] = $aSearch;
590 elseif (($sPhraseType == '' || $sPhraseType == 'street') && $aSearchTerm['class'] == 'place' && $aSearchTerm['type'] == 'house')
592 if ($aSearch['sHouseNumber'] === '')
594 $aSearch['sHouseNumber'] = $sToken;
595 if ($aSearch['iSearchRank'] < $iMaxRank) $aNewWordsetSearches[] = $aSearch;
597 // Fall back to not searching for this item (better than nothing)
598 $aSearch = $aCurrentSearch;
599 $aSearch['iSearchRank'] += 1;
600 if ($aSearch['iSearchRank'] < $iMaxRank) $aNewWordsetSearches[] = $aSearch;
604 elseif ($sPhraseType == '' && $aSearchTerm['class'] !== '' && $aSearchTerm['class'] !== null)
606 if ($aSearch['sClass'] === '')
608 $aSearch['sOperator'] = $aSearchTerm['operator'];
609 $aSearch['sClass'] = $aSearchTerm['class'];
610 $aSearch['sType'] = $aSearchTerm['type'];
611 if (sizeof($aSearch['aName'])) $aSearch['sOperator'] = 'name';
612 else $aSearch['sOperator'] = 'near'; // near = in for the moment
614 // Do we have a shortcut id?
615 if ($aSearch['sOperator'] == 'name')
617 $sSQL = "select get_tagpair('".$aSearch['sClass']."', '".$aSearch['sType']."')";
618 if ($iAmenityID = $oDB->getOne($sSQL))
620 $aValidTokens[$aSearch['sClass'].':'.$aSearch['sType']] = array('word_id' => $iAmenityID);
621 $aSearch['aName'][$iAmenityID] = $iAmenityID;
622 $aSearch['sClass'] = '';
623 $aSearch['sType'] = '';
626 if ($aSearch['iSearchRank'] < $iMaxRank) $aNewWordsetSearches[] = $aSearch;
629 elseif (isset($aSearchTerm['word_id']) && $aSearchTerm['word_id'])
631 if (sizeof($aSearch['aName']))
633 if ((!$bStructuredPhrases || $iPhrase > 0) && $sPhraseType != 'country' && (!isset($aValidTokens[$sToken]) || strlen($sToken) < 4 || strpos($sToken, ' ') !== false))
635 $aSearch['aAddress'][$aSearchTerm['word_id']] = $aSearchTerm['word_id'];
639 $aCurrentSearch['aFullNameAddress'][$aSearchTerm['word_id']] = $aSearchTerm['word_id'];
640 $aSearch['iSearchRank'] += 1000; // skip;
645 $aSearch['aName'][$aSearchTerm['word_id']] = $aSearchTerm['word_id'];
646 //$aSearch['iNamePhrase'] = $iPhrase;
648 if ($aSearch['iSearchRank'] < $iMaxRank) $aNewWordsetSearches[] = $aSearch;
652 if (isset($aValidTokens[$sToken]))
654 // Allow searching for a word - but at extra cost
655 foreach($aValidTokens[$sToken] as $aSearchTerm)
657 if (isset($aSearchTerm['word_id']) && $aSearchTerm['word_id'])
659 if ((!$bStructuredPhrases || $iPhrase > 0) && sizeof($aCurrentSearch['aName']) && strlen($sToken) >= 4)
661 $aSearch = $aCurrentSearch;
662 $aSearch['iSearchRank'] += 1;
663 if ($aWordFrequencyScores[$aSearchTerm['word_id']] < CONST_Max_Word_Frequency)
665 $aSearch['aAddress'][$aSearchTerm['word_id']] = $aSearchTerm['word_id'];
666 if ($aSearch['iSearchRank'] < $iMaxRank) $aNewWordsetSearches[] = $aSearch;
668 elseif (isset($aValidTokens[' '.$sToken])) // revert to the token version?
670 foreach($aValidTokens[' '.$sToken] as $aSearchTermToken)
672 if (empty($aSearchTermToken['country_code'])
673 && empty($aSearchTermToken['lat'])
674 && empty($aSearchTermToken['class']))
676 $aSearch = $aCurrentSearch;
677 $aSearch['iSearchRank'] += 1;
678 $aSearch['aAddress'][$aSearchTermToken['word_id']] = $aSearchTermToken['word_id'];
679 if ($aSearch['iSearchRank'] < $iMaxRank) $aNewWordsetSearches[] = $aSearch;
685 $aSearch['aAddressNonSearch'][$aSearchTerm['word_id']] = $aSearchTerm['word_id'];
686 if ($aSearch['iSearchRank'] < $iMaxRank) $aNewWordsetSearches[] = $aSearch;
690 if (!sizeof($aCurrentSearch['aName']) || $aCurrentSearch['iNamePhrase'] == $iPhrase)
692 $aSearch = $aCurrentSearch;
693 $aSearch['iSearchRank'] += 2;
694 if (preg_match('#^[0-9]+$#', $sToken)) $aSearch['iSearchRank'] += 2;
695 if ($aWordFrequencyScores[$aSearchTerm['word_id']] < CONST_Max_Word_Frequency)
696 $aSearch['aName'][$aSearchTerm['word_id']] = $aSearchTerm['word_id'];
698 $aSearch['aNameNonSearch'][$aSearchTerm['word_id']] = $aSearchTerm['word_id'];
699 $aSearch['iNamePhrase'] = $iPhrase;
700 if ($aSearch['iSearchRank'] < $iMaxRank) $aNewWordsetSearches[] = $aSearch;
707 // Allow skipping a word - but at EXTREAM cost
708 //$aSearch = $aCurrentSearch;
709 //$aSearch['iSearchRank']+=100;
710 //$aNewWordsetSearches[] = $aSearch;
714 usort($aNewWordsetSearches, 'bySearchRank');
715 $aWordsetSearches = array_slice($aNewWordsetSearches, 0, 50);
717 //var_Dump('<hr>',sizeof($aWordsetSearches)); exit;
719 $aNewPhraseSearches = array_merge($aNewPhraseSearches, $aNewWordsetSearches);
720 usort($aNewPhraseSearches, 'bySearchRank');
722 $aSearchHash = array();
723 foreach($aNewPhraseSearches as $iSearch => $aSearch)
725 $sHash = serialize($aSearch);
726 if (isset($aSearchHash[$sHash])) unset($aNewPhraseSearches[$iSearch]);
727 else $aSearchHash[$sHash] = 1;
730 $aNewPhraseSearches = array_slice($aNewPhraseSearches, 0, 50);
733 // Re-group the searches by their score, junk anything over 20 as just not worth trying
734 $aGroupedSearches = array();
735 foreach($aNewPhraseSearches as $aSearch)
737 if ($aSearch['iSearchRank'] < $iMaxRank)
739 if (!isset($aGroupedSearches[$aSearch['iSearchRank']])) $aGroupedSearches[$aSearch['iSearchRank']] = array();
740 $aGroupedSearches[$aSearch['iSearchRank']][] = $aSearch;
743 ksort($aGroupedSearches);
746 $aSearches = array();
747 foreach($aGroupedSearches as $iScore => $aNewSearches)
749 $iSearchCount += sizeof($aNewSearches);
750 $aSearches = array_merge($aSearches, $aNewSearches);
751 if ($iSearchCount > 50) break;
754 //if (CONST_Debug) _debugDumpGroupedSearches($aGroupedSearches, $aValidTokens);
761 // Re-group the searches by their score, junk anything over 20 as just not worth trying
762 $aGroupedSearches = array();
763 foreach($aSearches as $aSearch)
765 if ($aSearch['iSearchRank'] < $iMaxRank)
767 if (!isset($aGroupedSearches[$aSearch['iSearchRank']])) $aGroupedSearches[$aSearch['iSearchRank']] = array();
768 $aGroupedSearches[$aSearch['iSearchRank']][] = $aSearch;
771 ksort($aGroupedSearches);
774 if (CONST_Debug) var_Dump($aGroupedSearches);
778 $aCopyGroupedSearches = $aGroupedSearches;
779 foreach($aCopyGroupedSearches as $iGroup => $aSearches)
781 foreach($aSearches as $iSearch => $aSearch)
783 if (sizeof($aSearch['aAddress']))
785 $iReverseItem = array_pop($aSearch['aAddress']);
786 if (isset($aPossibleMainWordIDs[$iReverseItem]))
788 $aSearch['aAddress'] = array_merge($aSearch['aAddress'], $aSearch['aName']);
789 $aSearch['aName'] = array($iReverseItem);
790 $aGroupedSearches[$iGroup][] = $aSearch;
792 //$aReverseSearch['aName'][$iReverseItem] = $iReverseItem;
793 //$aGroupedSearches[$iGroup][] = $aReverseSearch;
799 if (CONST_Search_TryDroppedAddressTerms && sizeof($aStructuredQuery) > 0)
801 $aCopyGroupedSearches = $aGroupedSearches;
802 foreach($aCopyGroupedSearches as $iGroup => $aSearches)
804 foreach($aSearches as $iSearch => $aSearch)
806 $aReductionsList = array($aSearch['aAddress']);
807 $iSearchRank = $aSearch['iSearchRank'];
808 while(sizeof($aReductionsList) > 0)
811 if ($iSearchRank > iMaxRank) break 3;
812 $aNewReductionsList = array();
813 foreach($aReductionsList as $aReductionsWordList)
815 for ($iReductionWord = 0; $iReductionWord < sizeof($aReductionsWordList); $iReductionWord++)
817 $aReductionsWordListResult = array_merge(array_slice($aReductionsWordList, 0, $iReductionWord), array_slice($aReductionsWordList, $iReductionWord+1));
818 $aReverseSearch = $aSearch;
819 $aSearch['aAddress'] = $aReductionsWordListResult;
820 $aSearch['iSearchRank'] = $iSearchRank;
821 $aGroupedSearches[$iSearchRank][] = $aReverseSearch;
822 if (sizeof($aReductionsWordListResult) > 0)
824 $aNewReductionsList[] = $aReductionsWordListResult;
828 $aReductionsList = $aNewReductionsList;
832 ksort($aGroupedSearches);
835 // Filter out duplicate searches
836 $aSearchHash = array();
837 foreach($aGroupedSearches as $iGroup => $aSearches)
839 foreach($aSearches as $iSearch => $aSearch)
841 $sHash = serialize($aSearch);
842 if (isset($aSearchHash[$sHash]))
844 unset($aGroupedSearches[$iGroup][$iSearch]);
845 if (sizeof($aGroupedSearches[$iGroup]) == 0) unset($aGroupedSearches[$iGroup]);
849 $aSearchHash[$sHash] = 1;
854 if (CONST_Debug) _debugDumpGroupedSearches($aGroupedSearches, $aValidTokens);
858 foreach($aGroupedSearches as $iGroupedRank => $aSearches)
861 foreach($aSearches as $aSearch)
865 if (CONST_Debug) { echo "<hr><b>Search Loop, group $iGroupLoop, loop $iQueryLoop</b>"; }
866 if (CONST_Debug) _debugDumpGroupedSearches(array($iGroupedRank => array($aSearch)), $aValidTokens);
869 // Must have a location term
870 if (!sizeof($aSearch['aName']) && !sizeof($aSearch['aAddress']) && !$aSearch['fLon'])
872 if ($aSearch['sCountryCode'] && !$aSearch['sClass'] && !$aSearch['sHouseNumber'])
874 if (4 >= $iMinAddressRank && 4 <= $iMaxAddressRank)
876 $sSQL = "select place_id from placex where calculated_country_code='".$aSearch['sCountryCode']."' and rank_search = 4";
877 if ($sCountryCodesSQL) $sSQL .= " and calculated_country_code in ($sCountryCodesSQL)";
878 $sSQL .= " order by st_area(geometry) desc limit 1";
879 if (CONST_Debug) var_dump($sSQL);
880 $aPlaceIDs = $oDB->getCol($sSQL);
885 if (!$bBoundingBoxSearch && !$aSearch['fLon']) continue;
886 if (!$aSearch['sClass']) continue;
887 $sSQL = "select count(*) from pg_tables where tablename = 'place_classtype_".$aSearch['sClass']."_".$aSearch['sType']."'";
888 if ($oDB->getOne($sSQL))
890 $sSQL = "select place_id from place_classtype_".$aSearch['sClass']."_".$aSearch['sType']." ct";
891 if ($sCountryCodesSQL) $sSQL .= " join placex using (place_id)";
892 $sSQL .= " where st_contains($sViewboxSmallSQL, ct.centroid)";
893 if ($sCountryCodesSQL) $sSQL .= " and calculated_country_code in ($sCountryCodesSQL)";
894 if (sizeof($aExcludePlaceIDs))
896 $sSQL .= " and place_id not in (".join(',',$aExcludePlaceIDs).")";
898 if ($sViewboxCentreSQL) $sSQL .= " order by st_distance($sViewboxCentreSQL, ct.centroid) asc";
899 $sSQL .= " limit $iLimit";
900 if (CONST_Debug) var_dump($sSQL);
901 $aPlaceIDs = $oDB->getCol($sSQL);
903 // If excluded place IDs are given, it is fair to assume that
904 // there have been results in the small box, so no further
905 // expansion in that case.
906 if (!sizeof($aPlaceIDs) && !sizeof($aExcludePlaceIDs))
908 $sSQL = "select place_id from place_classtype_".$aSearch['sClass']."_".$aSearch['sType']." ct";
909 if ($sCountryCodesSQL) $sSQL .= " join placex using (place_id)";
910 $sSQL .= " where st_contains($sViewboxLargeSQL, ct.centroid)";
911 if ($sCountryCodesSQL) $sSQL .= " and calculated_country_code in ($sCountryCodesSQL)";
912 if ($sViewboxCentreSQL) $sSQL .= " order by st_distance($sViewboxCentreSQL, ct.centroid) asc";
913 $sSQL .= " limit $iLimit";
914 if (CONST_Debug) var_dump($sSQL);
915 $aPlaceIDs = $oDB->getCol($sSQL);
920 $sSQL = "select place_id from placex where class='".$aSearch['sClass']."' and type='".$aSearch['sType']."'";
921 $sSQL .= " and st_contains($sViewboxSmallSQL, geometry) and linked_place_id is null";
922 if ($sCountryCodesSQL) $sSQL .= " and calculated_country_code in ($sCountryCodesSQL)";
923 if ($sViewboxCentreSQL) $sSQL .= " order by st_distance($sViewboxCentreSQL, centroid) asc";
924 $sSQL .= " limit $iLimit";
925 if (CONST_Debug) var_dump($sSQL);
926 $aPlaceIDs = $oDB->getCol($sSQL);
932 $aPlaceIDs = array();
934 // First we need a position, either aName or fLat or both
938 // TODO: filter out the pointless search terms (2 letter name tokens and less)
939 // they might be right - but they are just too darned expensive to run
940 if (sizeof($aSearch['aName'])) $aTerms[] = "name_vector @> ARRAY[".join($aSearch['aName'],",")."]";
941 if (sizeof($aSearch['aNameNonSearch'])) $aTerms[] = "array_cat(name_vector,ARRAY[]::integer[]) @> ARRAY[".join($aSearch['aNameNonSearch'],",")."]";
942 if (sizeof($aSearch['aAddress']) && $aSearch['aName'] != $aSearch['aAddress'])
944 // For infrequent name terms disable index usage for address
945 if (CONST_Search_NameOnlySearchFrequencyThreshold &&
946 sizeof($aSearch['aName']) == 1 &&
947 $aWordFrequencyScores[$aSearch['aName'][reset($aSearch['aName'])]] < CONST_Search_NameOnlySearchFrequencyThreshold)
949 $aTerms[] = "array_cat(nameaddress_vector,ARRAY[]::integer[]) @> ARRAY[".join(array_merge($aSearch['aAddress'],$aSearch['aAddressNonSearch']),",")."]";
953 $aTerms[] = "nameaddress_vector @> ARRAY[".join($aSearch['aAddress'],",")."]";
954 if (sizeof($aSearch['aAddressNonSearch'])) $aTerms[] = "array_cat(nameaddress_vector,ARRAY[]::integer[]) @> ARRAY[".join($aSearch['aAddressNonSearch'],",")."]";
957 if ($aSearch['sCountryCode']) $aTerms[] = "country_code = '".pg_escape_string($aSearch['sCountryCode'])."'";
958 if ($aSearch['sHouseNumber']) $aTerms[] = "address_rank between 16 and 27";
959 if ($aSearch['fLon'] && $aSearch['fLat'])
961 $aTerms[] = "ST_DWithin(centroid, ST_SetSRID(ST_Point(".$aSearch['fLon'].",".$aSearch['fLat']."),4326), ".$aSearch['fRadius'].")";
962 $aOrder[] = "ST_Distance(centroid, ST_SetSRID(ST_Point(".$aSearch['fLon'].",".$aSearch['fLat']."),4326)) ASC";
964 if (sizeof($aExcludePlaceIDs))
966 $aTerms[] = "place_id not in (".join(',',$aExcludePlaceIDs).")";
968 if ($sCountryCodesSQL)
970 $aTerms[] = "country_code in ($sCountryCodesSQL)";
973 if ($bBoundingBoxSearch) $aTerms[] = "centroid && $sViewboxSmallSQL";
974 if ($sNearPointSQL) $aOrder[] = "ST_Distance($sNearPointSQL, centroid) asc";
976 $sImportanceSQL = '(case when importance = 0 OR importance IS NULL then 0.75-(search_rank::float/40) else importance end)';
977 if ($sViewboxSmallSQL) $sImportanceSQL .= " * case when ST_Contains($sViewboxSmallSQL, centroid) THEN 1 ELSE 0.5 END";
978 if ($sViewboxLargeSQL) $sImportanceSQL .= " * case when ST_Contains($sViewboxLargeSQL, centroid) THEN 1 ELSE 0.5 END";
979 $aOrder[] = "$sImportanceSQL DESC";
980 if (sizeof($aSearch['aFullNameAddress']))
982 $aOrder[] = '(select count(*) from (select unnest(ARRAY['.join($aSearch['aFullNameAddress'],",").']) INTERSECT select unnest(nameaddress_vector))s) DESC';
987 $sSQL = "select place_id";
988 $sSQL .= " from search_name";
989 $sSQL .= " where ".join(' and ',$aTerms);
990 $sSQL .= " order by ".join(', ',$aOrder);
991 if ($aSearch['sHouseNumber'] || $aSearch['sClass'])
992 $sSQL .= " limit 50";
993 elseif (!sizeof($aSearch['aName']) && !sizeof($aSearch['aAddress']) && $aSearch['sClass'])
996 $sSQL .= " limit ".$iLimit;
998 if (CONST_Debug) { var_dump($sSQL); }
999 $aViewBoxPlaceIDs = $oDB->getAll($sSQL);
1000 if (PEAR::IsError($aViewBoxPlaceIDs))
1002 failInternalError("Could not get places for search terms.", $sSQL, $aViewBoxPlaceIDs);
1004 //var_dump($aViewBoxPlaceIDs);
1005 // Did we have an viewbox matches?
1006 $aPlaceIDs = array();
1007 $bViewBoxMatch = false;
1008 foreach($aViewBoxPlaceIDs as $aViewBoxRow)
1010 //if ($bViewBoxMatch == 1 && $aViewBoxRow['in_small'] == 'f') break;
1011 //if ($bViewBoxMatch == 2 && $aViewBoxRow['in_large'] == 'f') break;
1012 //if ($aViewBoxRow['in_small'] == 't') $bViewBoxMatch = 1;
1013 //else if ($aViewBoxRow['in_large'] == 't') $bViewBoxMatch = 2;
1014 $aPlaceIDs[] = $aViewBoxRow['place_id'];
1017 //var_Dump($aPlaceIDs);
1020 if ($aSearch['sHouseNumber'] && sizeof($aPlaceIDs))
1022 $aRoadPlaceIDs = $aPlaceIDs;
1023 $sPlaceIDs = join(',',$aPlaceIDs);
1025 // Now they are indexed look for a house attached to a street we found
1026 $sHouseNumberRegex = '\\\\m'.str_replace(' ','[-,/ ]',$aSearch['sHouseNumber']).'\\\\M';
1027 $sSQL = "select place_id from placex where parent_place_id in (".$sPlaceIDs.") and housenumber ~* E'".$sHouseNumberRegex."'";
1028 if (sizeof($aExcludePlaceIDs))
1030 $sSQL .= " and place_id not in (".join(',',$aExcludePlaceIDs).")";
1032 $sSQL .= " limit $iLimit";
1033 if (CONST_Debug) var_dump($sSQL);
1034 $aPlaceIDs = $oDB->getCol($sSQL);
1036 // If not try the aux fallback table
1037 if (!sizeof($aPlaceIDs))
1039 $sSQL = "select place_id from location_property_aux where parent_place_id in (".$sPlaceIDs.") and housenumber = '".pg_escape_string($aSearch['sHouseNumber'])."'";
1040 if (sizeof($aExcludePlaceIDs))
1042 $sSQL .= " and place_id not in (".join(',',$aExcludePlaceIDs).")";
1044 //$sSQL .= " limit $iLimit";
1045 if (CONST_Debug) var_dump($sSQL);
1046 $aPlaceIDs = $oDB->getCol($sSQL);
1049 if (!sizeof($aPlaceIDs))
1051 $sSQL = "select place_id from location_property_tiger where parent_place_id in (".$sPlaceIDs.") and housenumber = '".pg_escape_string($aSearch['sHouseNumber'])."'";
1052 if (sizeof($aExcludePlaceIDs))
1054 $sSQL .= " and place_id not in (".join(',',$aExcludePlaceIDs).")";
1056 //$sSQL .= " limit $iLimit";
1057 if (CONST_Debug) var_dump($sSQL);
1058 $aPlaceIDs = $oDB->getCol($sSQL);
1061 // Fallback to the road
1062 if (!sizeof($aPlaceIDs) && preg_match('/[0-9]+/', $aSearch['sHouseNumber']))
1064 $aPlaceIDs = $aRoadPlaceIDs;
1069 if ($aSearch['sClass'] && sizeof($aPlaceIDs))
1071 $sPlaceIDs = join(',',$aPlaceIDs);
1072 $aClassPlaceIDs = array();
1074 if (!$aSearch['sOperator'] || $aSearch['sOperator'] == 'name')
1076 // If they were searching for a named class (i.e. 'Kings Head pub') then we might have an extra match
1077 $sSQL = "select place_id from placex where place_id in ($sPlaceIDs) and class='".$aSearch['sClass']."' and type='".$aSearch['sType']."'";
1078 $sSQL .= " and linked_place_id is null";
1079 if ($sCountryCodesSQL) $sSQL .= " and calculated_country_code in ($sCountryCodesSQL)";
1080 $sSQL .= " order by rank_search asc limit $iLimit";
1081 if (CONST_Debug) var_dump($sSQL);
1082 $aClassPlaceIDs = $oDB->getCol($sSQL);
1085 if (!$aSearch['sOperator'] || $aSearch['sOperator'] == 'near') // & in
1087 $sSQL = "select count(*) from pg_tables where tablename = 'place_classtype_".$aSearch['sClass']."_".$aSearch['sType']."'";
1088 $bCacheTable = $oDB->getOne($sSQL);
1090 $sSQL = "select min(rank_search) from placex where place_id in ($sPlaceIDs)";
1092 if (CONST_Debug) var_dump($sSQL);
1093 $iMaxRank = ((int)$oDB->getOne($sSQL));
1095 // For state / country level searches the normal radius search doesn't work very well
1096 $sPlaceGeom = false;
1097 if ($iMaxRank < 9 && $bCacheTable)
1099 // Try and get a polygon to search in instead
1100 $sSQL = "select geometry from placex where place_id in ($sPlaceIDs) and rank_search < $iMaxRank + 5 and st_geometrytype(geometry) in ('ST_Polygon','ST_MultiPolygon') order by rank_search asc limit 1";
1101 if (CONST_Debug) var_dump($sSQL);
1102 $sPlaceGeom = $oDB->getOne($sSQL);
1112 $sSQL = "select place_id from placex where place_id in ($sPlaceIDs) and rank_search < $iMaxRank";
1113 if (CONST_Debug) var_dump($sSQL);
1114 $aPlaceIDs = $oDB->getCol($sSQL);
1115 $sPlaceIDs = join(',',$aPlaceIDs);
1118 if ($sPlaceIDs || $sPlaceGeom)
1124 // More efficient - can make the range bigger
1128 if ($sNearPointSQL) $sOrderBySQL = "ST_Distance($sNearPointSQL, l.centroid)";
1129 else if ($sPlaceIDs) $sOrderBySQL = "ST_Distance(l.centroid, f.geometry)";
1130 else if ($sPlaceGeom) $sOrderBysSQL = "ST_Distance(st_centroid('".$sPlaceGeom."'), l.centroid)";
1132 $sSQL = "select distinct l.place_id".($sOrderBySQL?','.$sOrderBySQL:'')." from place_classtype_".$aSearch['sClass']."_".$aSearch['sType']." as l";
1133 if ($sCountryCodesSQL) $sSQL .= " join placex as lp using (place_id)";
1136 $sSQL .= ",placex as f where ";
1137 $sSQL .= "f.place_id in ($sPlaceIDs) and ST_DWithin(l.centroid, f.centroid, $fRange) ";
1142 $sSQL .= "ST_Contains('".$sPlaceGeom."', l.centroid) ";
1144 if (sizeof($aExcludePlaceIDs))
1146 $sSQL .= " and l.place_id not in (".join(',',$aExcludePlaceIDs).")";
1148 if ($sCountryCodesSQL) $sSQL .= " and lp.calculated_country_code in ($sCountryCodesSQL)";
1149 if ($sOrderBySQL) $sSQL .= "order by ".$sOrderBySQL." asc";
1150 if ($iOffset) $sSQL .= " offset $iOffset";
1151 $sSQL .= " limit $iLimit";
1152 if (CONST_Debug) var_dump($sSQL);
1153 $aClassPlaceIDs = array_merge($aClassPlaceIDs, $oDB->getCol($sSQL));
1157 if (isset($aSearch['fRadius']) && $aSearch['fRadius']) $fRange = $aSearch['fRadius'];
1160 if ($sNearPointSQL) $sOrderBySQL = "ST_Distance($sNearPointSQL, l.geometry)";
1161 else $sOrderBySQL = "ST_Distance(l.geometry, f.geometry)";
1163 $sSQL = "select distinct l.place_id".($sOrderBysSQL?','.$sOrderBysSQL:'')." from placex as l,placex as f where ";
1164 $sSQL .= "f.place_id in ( $sPlaceIDs) and ST_DWithin(l.geometry, f.centroid, $fRange) ";
1165 $sSQL .= "and l.class='".$aSearch['sClass']."' and l.type='".$aSearch['sType']."' ";
1166 if (sizeof($aExcludePlaceIDs))
1168 $sSQL .= " and l.place_id not in (".join(',',$aExcludePlaceIDs).")";
1170 if ($sCountryCodesSQL) $sSQL .= " and l.calculated_country_code in ($sCountryCodesSQL)";
1171 if ($sOrderBy) $sSQL .= "order by ".$OrderBysSQL." asc";
1172 if ($iOffset) $sSQL .= " offset $iOffset";
1173 $sSQL .= " limit $iLimit";
1174 if (CONST_Debug) var_dump($sSQL);
1175 $aClassPlaceIDs = array_merge($aClassPlaceIDs, $oDB->getCol($sSQL));
1180 $aPlaceIDs = $aClassPlaceIDs;
1186 if (PEAR::IsError($aPlaceIDs))
1188 failInternalError("Could not get place IDs from tokens." ,$sSQL, $aPlaceIDs);
1191 if (CONST_Debug) { echo "<br><b>Place IDs:</b> "; var_Dump($aPlaceIDs); }
1193 foreach($aPlaceIDs as $iPlaceID)
1195 $aResultPlaceIDs[$iPlaceID] = $iPlaceID;
1197 if ($iQueryLoop > 20) break;
1200 if (isset($aResultPlaceIDs) && sizeof($aResultPlaceIDs) && ($iMinAddressRank != 0 || $iMaxAddressRank != 30))
1202 // Need to verify passes rank limits before dropping out of the loop (yuk!)
1203 $sSQL = "select place_id from placex where place_id in (".join(',',$aResultPlaceIDs).") ";
1204 $sSQL .= "and (placex.rank_address between $iMinAddressRank and $iMaxAddressRank ";
1205 if (14 >= $iMinAddressRank && 14 <= $iMaxAddressRank) $sSQL .= " OR (extratags->'place') = 'city'";
1206 if ($aAddressRankList) $sSQL .= " OR placex.rank_address in (".join(',',$aAddressRankList).")";
1207 $sSQL .= ") UNION select place_id from location_property_tiger where place_id in (".join(',',$aResultPlaceIDs).") ";
1208 $sSQL .= "and (30 between $iMinAddressRank and $iMaxAddressRank ";
1209 if ($aAddressRankList) $sSQL .= " OR 30 in (".join(',',$aAddressRankList).")";
1211 if (CONST_Debug) var_dump($sSQL);
1212 $aResultPlaceIDs = $oDB->getCol($sSQL);
1217 if (isset($aResultPlaceIDs) && sizeof($aResultPlaceIDs)) break;
1218 if ($iGroupLoop > 4) break;
1219 if ($iQueryLoop > 30) break;
1222 // Did we find anything?
1223 if (isset($aResultPlaceIDs) && sizeof($aResultPlaceIDs))
1225 //var_Dump($aResultPlaceIDs);exit;
1226 // Get the details for display (is this a redundant extra step?)
1227 $sPlaceIDs = join(',',$aResultPlaceIDs);
1228 $sOrderSQL = 'CASE ';
1229 foreach(array_keys($aResultPlaceIDs) as $iOrder => $iPlaceID)
1231 $sOrderSQL .= 'when min(place_id) = '.$iPlaceID.' then '.$iOrder.' ';
1233 $sOrderSQL .= ' ELSE 10000000 END';
1234 $sSQL = "select osm_type,osm_id,class,type,admin_level,rank_search,rank_address,min(place_id) as place_id,calculated_country_code as country_code,";
1235 $sSQL .= "get_address_by_language(place_id, $sLanguagePrefArraySQL) as langaddress,";
1236 $sSQL .= "get_name_by_language(name, $sLanguagePrefArraySQL) as placename,";
1237 $sSQL .= "get_name_by_language(name, ARRAY['ref']) as ref,";
1238 $sSQL .= "avg(ST_X(centroid)) as lon,avg(ST_Y(centroid)) as lat, ";
1239 //$sSQL .= $sOrderSQL." as porder, ";
1240 $sSQL .= "coalesce(importance,0.75-(rank_search::float/40)) as importance, ";
1241 $sSQL .= "(select max(p.importance*(p.rank_address+2)) from place_addressline s, placex p where s.place_id = min(placex.place_id) and p.place_id = s.address_place_id and s.isaddress and p.importance is not null) as addressimportance, ";
1242 $sSQL .= "(extratags->'place') as extra_place ";
1243 $sSQL .= "from placex where place_id in ($sPlaceIDs) ";
1244 $sSQL .= "and (placex.rank_address between $iMinAddressRank and $iMaxAddressRank ";
1245 if (14 >= $iMinAddressRank && 14 <= $iMaxAddressRank) $sSQL .= " OR (extratags->'place') = 'city'";
1246 if ($aAddressRankList) $sSQL .= " OR placex.rank_address in (".join(',',$aAddressRankList).")";
1248 if ($sAllowedTypesSQLList) $sSQL .= "and placex.class in $sAllowedTypesSQLList ";
1249 $sSQL .= "and linked_place_id is null ";
1250 $sSQL .= "group by osm_type,osm_id,class,type,admin_level,rank_search,rank_address,calculated_country_code,importance";
1251 if (!$bDeDupe) $sSQL .= ",place_id";
1252 $sSQL .= ",langaddress ";
1253 $sSQL .= ",placename ";
1255 $sSQL .= ",extratags->'place' ";
1257 $sSQL .= "select 'T' as osm_type,place_id as osm_id,'place' as class,'house' as type,null as admin_level,30 as rank_search,30 as rank_address,min(place_id) as place_id,'us' as country_code,";
1258 $sSQL .= "get_address_by_language(place_id, $sLanguagePrefArraySQL) as langaddress,";
1259 $sSQL .= "null as placename,";
1260 $sSQL .= "null as ref,";
1261 $sSQL .= "avg(ST_X(centroid)) as lon,avg(ST_Y(centroid)) as lat, ";
1262 //$sSQL .= $sOrderSQL." as porder, ";
1263 $sSQL .= "-0.15 as importance, ";
1264 $sSQL .= "(select max(p.importance*(p.rank_address+2)) from place_addressline s, placex p where s.place_id = min(location_property_tiger.place_id) and p.place_id = s.address_place_id and s.isaddress and p.importance is not null) as addressimportance, ";
1265 $sSQL .= "null as extra_place ";
1266 $sSQL .= "from location_property_tiger where place_id in ($sPlaceIDs) ";
1267 $sSQL .= "and 30 between $iMinAddressRank and $iMaxAddressRank ";
1268 $sSQL .= "group by place_id";
1269 if (!$bDeDupe) $sSQL .= ",place_id";
1271 $sSQL .= "select 'L' as osm_type,place_id as osm_id,'place' as class,'house' as type,null as admin_level,30 as rank_search,30 as rank_address,min(place_id) as place_id,'us' as country_code,";
1272 $sSQL .= "get_address_by_language(place_id, $sLanguagePrefArraySQL) as langaddress,";
1273 $sSQL .= "null as placename,";
1274 $sSQL .= "null as ref,";
1275 $sSQL .= "avg(ST_X(centroid)) as lon,avg(ST_Y(centroid)) as lat, ";
1276 //$sSQL .= $sOrderSQL." as porder, ";
1277 $sSQL .= "-0.10 as importance, ";
1278 $sSQL .= "(select max(p.importance*(p.rank_address+2)) from place_addressline s, placex p where s.place_id = min(location_property_aux.place_id) and p.place_id = s.address_place_id and s.isaddress and p.importance is not null) as addressimportance, ";
1279 $sSQL .= "null as extra_place ";
1280 $sSQL .= "from location_property_aux where place_id in ($sPlaceIDs) ";
1281 $sSQL .= "and 30 between $iMinAddressRank and $iMaxAddressRank ";
1282 $sSQL .= "group by place_id";
1283 if (!$bDeDupe) $sSQL .= ",place_id";
1284 $sSQL .= ",get_address_by_language(place_id, $sLanguagePrefArraySQL) ";
1285 $sSQL .= "order by importance desc";
1286 //$sSQL .= "order by rank_search,rank_address,porder asc";
1287 if (CONST_Debug) { echo "<hr>"; var_dump($sSQL); }
1288 $aSearchResults = $oDB->getAll($sSQL);
1289 //var_dump($sSQL,$aSearchResults);exit;
1291 if (PEAR::IsError($aSearchResults))
1293 failInternalError("Could not get details for place.", $sSQL, $aSearchResults);
1296 } // end if ($sQuery)
1299 if (isset($_GET['nearlat']) && trim($_GET['nearlat'])!=='' && isset($_GET['nearlon']) && trim($_GET['nearlon']) !== '')
1301 $iPlaceID = geocodeReverse((float)$_GET['nearlat'], (float)$_GET['nearlon']);
1305 $aResultPlaceIDs = array($iPlaceID);
1306 // TODO: this needs refactoring!
1308 // Get the details for display (is this a redundant extra step?)
1309 $sPlaceIDs = join(',',$aResultPlaceIDs);
1310 $sOrderSQL = 'CASE ';
1311 foreach(array_keys($aResultPlaceIDs) as $iOrder => $iPlaceID)
1313 $sOrderSQL .= 'when min(place_id) = '.$iPlaceID.' then '.$iOrder.' ';
1315 $sOrderSQL .= ' ELSE 10000000 END';
1316 $sSQL = "select osm_type,osm_id,class,type,admin_level,rank_search,rank_address,min(place_id) as place_id,calculated_country_code as country_code,";
1317 $sSQL .= "get_address_by_language(place_id, $sLanguagePrefArraySQL) as langaddress,";
1318 $sSQL .= "get_name_by_language(name, $sLanguagePrefArraySQL) as placename,";
1319 $sSQL .= "get_name_by_language(name, ARRAY['ref']) as ref,";
1320 $sSQL .= "avg(ST_X(centroid)) as lon,avg(ST_Y(centroid)) as lat, ";
1321 //$sSQL .= $sOrderSQL." as porder, ";
1322 $sSQL .= "coalesce(importance,0.75-(rank_search::float/40)) as importance, ";
1323 $sSQL .= "(extratags->'place') as extra_place ";
1324 $sSQL .= "from placex where place_id in ($sPlaceIDs) ";
1325 $sSQL .= "and (placex.rank_address between $iMinAddressRank and $iMaxAddressRank ";
1326 if (14 >= $iMinAddressRank && 14 <= $iMaxAddressRank) $sSQL .= " OR (extratags->'place') = 'city'";
1327 if ($aAddressRankList) $sSQL .= " OR placex.rank_address in (".join(',',$aAddressRankList).")";
1329 $sSQL .= "group by osm_type,osm_id,class,type,admin_level,rank_search,rank_address,calculated_country_code,importance";
1330 if (!$bDeDupe) $sSQL .= ",place_id";
1331 $sSQL .= ",get_address_by_language(place_id, $sLanguagePrefArraySQL) ";
1332 $sSQL .= ",get_name_by_language(name, $sLanguagePrefArraySQL) ";
1333 $sSQL .= ",get_name_by_language(name, ARRAY['ref']) ";
1334 $sSQL .= ",extratags->'place' ";
1336 $sSQL .= "select 'T' as osm_type,place_id as osm_id,'place' as class,'house' as type,null as admin_level,30 as rank_search,30 as rank_address,min(place_id) as place_id,'us' as country_code,";
1337 $sSQL .= "get_address_by_language(place_id, $sLanguagePrefArraySQL) as langaddress,";
1338 $sSQL .= "null as placename,";
1339 $sSQL .= "null as ref,";
1340 $sSQL .= "avg(ST_X(centroid)) as lon,avg(ST_Y(centroid)) as lat, ";
1341 //$sSQL .= $sOrderSQL." as porder, ";
1342 $sSQL .= "-0.15 as importance, ";
1343 $sSQL .= "null as extra_place ";
1344 $sSQL .= "from location_property_tiger where place_id in ($sPlaceIDs) ";
1345 $sSQL .= "and 30 between $iMinAddressRank and $iMaxAddressRank ";
1346 $sSQL .= "group by place_id";
1347 if (!$bDeDupe) $sSQL .= ",place_id";
1349 $sSQL .= "select 'L' as osm_type,place_id as osm_id,'place' as class,'house' as type,null as admin_level,30 as rank_search,30 as rank_address,min(place_id) as place_id,'us' as country_code,";
1350 $sSQL .= "get_address_by_language(place_id, $sLanguagePrefArraySQL) as langaddress,";
1351 $sSQL .= "null as placename,";
1352 $sSQL .= "null as ref,";
1353 $sSQL .= "avg(ST_X(centroid)) as lon,avg(ST_Y(centroid)) as lat, ";
1354 //$sSQL .= $sOrderSQL." as porder, ";
1355 $sSQL .= "-0.10 as importance, ";
1356 $sSQL .= "null as extra_place ";
1357 $sSQL .= "from location_property_aux where place_id in ($sPlaceIDs) ";
1358 $sSQL .= "and 30 between $iMinAddressRank and $iMaxAddressRank ";
1359 $sSQL .= "group by place_id";
1360 if (!$bDeDupe) $sSQL .= ",place_id";
1361 $sSQL .= ",get_address_by_language(place_id, $sLanguagePrefArraySQL) ";
1362 $sSQL .= "order by importance desc";
1363 //$sSQL .= "order by rank_search,rank_address,porder asc";
1364 if (CONST_Debug) { echo "<hr>", var_dump($sSQL); }
1365 $aSearchResults = $oDB->getAll($sSQL);
1366 //var_dump($sSQL,$aSearchResults);exit;
1368 if (PEAR::IsError($aSearchResults))
1370 failInternalError("Could not get details for place (near).", $sSQL, $aSearchResults);
1375 $aSearchResults = array();
1381 $sSearchResult = '';
1382 if (!sizeof($aSearchResults) && isset($_GET['q']) && $_GET['q'])
1384 $sSearchResult = 'No Results Found';
1386 //var_Dump($aSearchResults);
1388 $aClassType = getClassTypesWithImportance();
1389 $aRecheckWords = preg_split('/\b/u',$sQuery);
1390 foreach($aRecheckWords as $i => $sWord)
1392 if (!$sWord) unset($aRecheckWords[$i]);
1394 foreach($aSearchResults as $iResNum => $aResult)
1396 if (CONST_Search_AreaPolygons)
1398 // Get the bounding box and outline polygon
1399 $sSQL = "select place_id,numfeatures,area,outline,";
1400 $sSQL .= "ST_Y(ST_PointN(ST_ExteriorRing(Box2D(outline)),4)) as minlat,ST_Y(ST_PointN(ST_ExteriorRing(Box2D(outline)),2)) as maxlat,";
1401 $sSQL .= "ST_X(ST_PointN(ST_ExteriorRing(Box2D(outline)),1)) as minlon,ST_X(ST_PointN(ST_ExteriorRing(Box2D(outline)),3)) as maxlon,";
1402 $sSQL .= "ST_AsText(outline) as outlinestring from get_place_boundingbox_quick(".$aResult['place_id'].")";
1404 $sSQL = "select place_id,0 as numfeatures,st_area(geometry) as area,";
1405 $sSQL .= "ST_Y(centroid) as centrelat,ST_X(centroid) as centrelon,";
1406 $sSQL .= "ST_Y(ST_PointN(ST_ExteriorRing(Box2D(geometry)),4)) as minlat,ST_Y(ST_PointN(ST_ExteriorRing(Box2D(geometry)),2)) as maxlat,";
1407 $sSQL .= "ST_X(ST_PointN(ST_ExteriorRing(Box2D(geometry)),1)) as minlon,ST_X(ST_PointN(ST_ExteriorRing(Box2D(geometry)),3)) as maxlon";
1408 if ($bAsGeoJSON) $sSQL .= ",ST_AsGeoJSON(geometry) as asgeojson";
1409 if ($bAsKML) $sSQL .= ",ST_AsKML(geometry) as askml";
1410 if ($bAsSVG) $sSQL .= ",ST_AsSVG(geometry) as assvg";
1411 if ($bAsText || $bShowPolygons) $sSQL .= ",ST_AsText(geometry) as astext";
1412 $sSQL .= " from placex where place_id = ".$aResult['place_id'].' and st_geometrytype(Box2D(geometry)) = \'ST_Polygon\'';
1413 $aPointPolygon = $oDB->getRow($sSQL);
1414 if (PEAR::IsError($aPointPolygon))
1416 failInternalError("Could not get outline.", $sSQL, $aPointPolygon);
1418 if ($aPointPolygon['place_id'])
1420 if ($bAsGeoJSON) $aResult['asgeojson'] = $aPointPolygon['asgeojson'];
1421 if ($bAsKML) $aResult['askml'] = $aPointPolygon['askml'];
1422 if ($bAsSVG) $aResult['assvg'] = $aPointPolygon['assvg'];
1423 if ($bAsText) $aResult['astext'] = $aPointPolygon['astext'];
1425 if ($aPointPolygon['centrelon'] !== null && $aPointPolygon['centrelat'] !== null )
1427 $aResult['lat'] = $aPointPolygon['centrelat'];
1428 $aResult['lon'] = $aPointPolygon['centrelon'];
1432 // Translate geometary string to point array
1433 if (preg_match('#POLYGON\\(\\(([- 0-9.,]+)#',$aPointPolygon['astext'],$aMatch))
1435 preg_match_all('/(-?[0-9.]+) (-?[0-9.]+)/',$aMatch[1],$aPolyPoints,PREG_SET_ORDER);
1437 elseif (preg_match('#MULTIPOLYGON\\(\\(\\(([- 0-9.,]+)#',$aPointPolygon['astext'],$aMatch))
1439 preg_match_all('/(-?[0-9.]+) (-?[0-9.]+)/',$aMatch[1],$aPolyPoints,PREG_SET_ORDER);
1441 elseif (preg_match('#POINT\\((-?[0-9.]+) (-?[0-9.]+)\\)#',$aPointPolygon['astext'],$aMatch))
1444 $iSteps = ($fRadius * 40000)^2;
1445 $fStepSize = (2*pi())/$iSteps;
1446 $aPolyPoints = array();
1447 for($f = 0; $f < 2*pi(); $f += $fStepSize)
1449 $aPolyPoints[] = array('',$aMatch[1]+($fRadius*sin($f)),$aMatch[2]+($fRadius*cos($f)));
1451 $aPointPolygon['minlat'] = $aPointPolygon['minlat'] - $fRadius;
1452 $aPointPolygon['maxlat'] = $aPointPolygon['maxlat'] + $fRadius;
1453 $aPointPolygon['minlon'] = $aPointPolygon['minlon'] - $fRadius;
1454 $aPointPolygon['maxlon'] = $aPointPolygon['maxlon'] + $fRadius;
1458 // Output data suitable for display (points and a bounding box)
1459 if ($bShowPolygons && isset($aPolyPoints))
1461 $aResult['aPolyPoints'] = array();
1462 foreach($aPolyPoints as $aPoint)
1464 $aResult['aPolyPoints'][] = array($aPoint[1], $aPoint[2]);
1467 $aResult['aBoundingBox'] = array($aPointPolygon['minlat'],$aPointPolygon['maxlat'],$aPointPolygon['minlon'],$aPointPolygon['maxlon']);
1471 if ($aResult['extra_place'] == 'city')
1473 $aResult['class'] = 'place';
1474 $aResult['type'] = 'city';
1475 $aResult['rank_search'] = 16;
1478 if (!isset($aResult['aBoundingBox']))
1481 $fDiameter = 0.0001;
1483 if (isset($aClassType[$aResult['class'].':'.$aResult['type'].':'.$aResult['admin_level']]['defdiameter'])
1484 && $aClassType[$aResult['class'].':'.$aResult['type'].':'.$aResult['admin_level']]['defdiameter'])
1486 $fDiameter = $aClassType[$aResult['class'].':'.$aResult['type'].':'.$aResult['admin_level']]['defzoom'];
1488 elseif (isset($aClassType[$aResult['class'].':'.$aResult['type']]['defdiameter'])
1489 && $aClassType[$aResult['class'].':'.$aResult['type']]['defdiameter'])
1491 $fDiameter = $aClassType[$aResult['class'].':'.$aResult['type']]['defdiameter'];
1493 $fRadius = $fDiameter / 2;
1495 $iSteps = max(8,min(100,$fRadius * 3.14 * 100000));
1496 $fStepSize = (2*pi())/$iSteps;
1497 $aPolyPoints = array();
1498 for($f = 0; $f < 2*pi(); $f += $fStepSize)
1500 $aPolyPoints[] = array('',$aResult['lon']+($fRadius*sin($f)),$aResult['lat']+($fRadius*cos($f)));
1502 $aPointPolygon['minlat'] = $aResult['lat'] - $fRadius;
1503 $aPointPolygon['maxlat'] = $aResult['lat'] + $fRadius;
1504 $aPointPolygon['minlon'] = $aResult['lon'] - $fRadius;
1505 $aPointPolygon['maxlon'] = $aResult['lon'] + $fRadius;
1507 // Output data suitable for display (points and a bounding box)
1510 $aResult['aPolyPoints'] = array();
1511 foreach($aPolyPoints as $aPoint)
1513 $aResult['aPolyPoints'][] = array($aPoint[1], $aPoint[2]);
1516 $aResult['aBoundingBox'] = array($aPointPolygon['minlat'],$aPointPolygon['maxlat'],$aPointPolygon['minlon'],$aPointPolygon['maxlon']);
1519 // Is there an icon set for this type of result?
1520 if (isset($aClassType[$aResult['class'].':'.$aResult['type']]['icon'])
1521 && $aClassType[$aResult['class'].':'.$aResult['type']]['icon'])
1523 $aResult['icon'] = CONST_Website_BaseURL.'images/mapicons/'.$aClassType[$aResult['class'].':'.$aResult['type']]['icon'].'.p.20.png';
1526 if (isset($aClassType[$aResult['class'].':'.$aResult['type']]['label'])
1527 && $aClassType[$aResult['class'].':'.$aResult['type']]['label'])
1529 $aResult['label'] = $aClassType[$aResult['class'].':'.$aResult['type']]['label'];
1532 if ($bShowAddressDetails)
1534 $aResult['address'] = getAddressDetails($oDB, $sLanguagePrefArraySQL, $aResult['place_id'], $aResult['country_code']);
1535 if ($aResult['extra_place'] == 'city' && !isset($aResult['address']['city']))
1537 $aResult['address'] = array_merge(array('city' => array_shift(array_values($aResult['address']))), $aResult['address']);
1540 //var_dump($aResult['address']);
1544 // Adjust importance for the number of exact string matches in the result
1545 $aResult['importance'] = max(0.001,$aResult['importance']);
1547 $sAddress = $aResult['langaddress'];
1548 foreach($aRecheckWords as $i => $sWord)
1550 if (stripos($sAddress, $sWord)!==false) $iCountWords++;
1553 $aResult['importance'] = $aResult['importance'] + ($iCountWords*0.1); // 0.1 is a completely arbitrary number but something in the range 0.1 to 0.5 would seem right
1555 //if (CONST_Debug) var_dump($aResult['class'].':'.$aResult['type'].':'.$aResult['admin_level']);
1557 if (isset($aClassType[$aResult['class'].':'.$aResult['type'].':'.$aResult['admin_level']]['importance'])
1558 && $aClassType[$aResult['class'].':'.$aResult['type'].':'.$aResult['admin_level']]['importance'])
1560 $aResult['importance'] = $aClassType[$aResult['class'].':'.$aResult['type'].':'.$aResult['admin_level']]['importance'];
1562 elseif (isset($aClassType[$aResult['class'].':'.$aResult['type']]['importance'])
1563 && $aClassType[$aResult['class'].':'.$aResult['type']]['importance'])
1565 $aResult['importance'] = $aClassType[$aResult['class'].':'.$aResult['type']]['importance'];
1569 $aResult['importance'] = 1000000000000000;
1572 $aResult['name'] = $aResult['langaddress'];
1573 $aResult['foundorder'] = -$aResult['addressimportance'];
1574 $aSearchResults[$iResNum] = $aResult;
1576 uasort($aSearchResults, 'byImportance');
1578 $aOSMIDDone = array();
1579 $aClassTypeNameDone = array();
1580 $aToFilter = $aSearchResults;
1581 $aSearchResults = array();
1584 foreach($aToFilter as $iResNum => $aResult)
1586 if ($aResult['type'] == 'adminitrative') $aResult['type'] = 'administrative';
1587 $aExcludePlaceIDs[$aResult['place_id']] = $aResult['place_id'];
1590 $fLat = $aResult['lat'];
1591 $fLon = $aResult['lon'];
1592 if (isset($aResult['zoom'])) $iZoom = $aResult['zoom'];
1595 if (!$bDeDupe || (!isset($aOSMIDDone[$aResult['osm_type'].$aResult['osm_id']])
1596 && !isset($aClassTypeNameDone[$aResult['osm_type'].$aResult['class'].$aResult['type'].$aResult['name'].$aResult['admin_level']])))
1598 $aOSMIDDone[$aResult['osm_type'].$aResult['osm_id']] = true;
1599 $aClassTypeNameDone[$aResult['osm_type'].$aResult['class'].$aResult['type'].$aResult['name'].$aResult['admin_level']] = true;
1600 $aSearchResults[] = $aResult;
1603 // Absolute limit on number of results
1604 if (sizeof($aSearchResults) >= $iFinalLimit) break;
1607 $sDataDate = $oDB->getOne("select TO_CHAR(lastimportdate - '2 minutes'::interval,'YYYY/MM/DD HH24:MI')||' GMT' from import_status limit 1");
1609 if (isset($_GET['nearlat']) && isset($_GET['nearlon']))
1611 $sQuery .= ' ['.$_GET['nearlat'].','.$_GET['nearlon'].']';
1616 logEnd($oDB, $hLog, sizeof($aToFilter));
1618 $sMoreURL = CONST_Website_BaseURL.'search?format='.urlencode($sOutputFormat).'&exclude_place_ids='.join(',',$aExcludePlaceIDs);
1619 if (isset($_SERVER["HTTP_ACCEPT_LANGUAGE"])) $sMoreURL .= '&accept-language='.$_SERVER["HTTP_ACCEPT_LANGUAGE"];
1620 if ($bShowPolygons) $sMoreURL .= '&polygon=1';
1621 if ($bShowAddressDetails) $sMoreURL .= '&addressdetails=1';
1622 if (isset($_GET['viewbox']) && $_GET['viewbox']) $sMoreURL .= '&viewbox='.urlencode($_GET['viewbox']);
1623 if (isset($_GET['nearlat']) && isset($_GET['nearlon'])) $sMoreURL .= '&nearlat='.(float)$_GET['nearlat'].'&nearlon='.(float)$_GET['nearlon'];
1624 $sMoreURL .= '&q='.urlencode($sQuery);
1626 if (CONST_Debug) exit;
1628 include(CONST_BasePath.'/lib/template/search-'.$sOutputFormat.'.php');