2 @define('CONST_ConnectionBucket_PageType', 'Search');
4 require_once(dirname(dirname(__FILE__)).'/lib/init-website.php');
5 require_once(CONST_BasePath.'/lib/log.php');
7 ini_set('memory_limit', '200M');
12 $fLat = CONST_Default_Lat;
13 $fLon = CONST_Default_Lon;
14 $iZoom = CONST_Default_Zoom;
15 $bBoundingBoxSearch = isset($_GET['bounded'])?(bool)$_GET['bounded']:false;
16 $sOutputFormat = 'html';
17 $aSearchResults = array();
18 $aExcludePlaceIDs = array();
19 $sCountryCodesSQL = false;
20 $bDeDupe = isset($_GET['dedupe'])?(bool)$_GET['dedupe']:true;
21 $bReverseInPlan = false;
22 $iFinalLimit = isset($_GET['limit'])?(int)$_GET['limit']:10;
23 $iOffset = isset($_GET['offset'])?(int)$_GET['offset']:0;
25 if ($iFinalLimit > 50) $iFinalLimit = 50;
26 $iLimit = $iFinalLimit + min($iFinalLimit, 10);
28 $iMaxAddressRank = 30;
29 $aAddressRankList = array();
30 $sAllowedTypesSQLList = false;
33 if (isset($_GET['format']) && ($_GET['format'] == 'html' || $_GET['format'] == 'xml' || $_GET['format'] == 'json' || $_GET['format'] == 'jsonv2'))
35 $sOutputFormat = $_GET['format'];
38 // Show / use polygons
39 $bShowPolygons = (boolean)isset($_GET['polygon']) && $_GET['polygon'];
40 if ($sOutputFormat == 'html')
42 $bAsText = $bShowPolygons;
43 $bShowPolygons = false;
50 $bAsGeoJSON = (boolean)isset($_GET['polygon_geojson']) && $_GET['polygon_geojson'];
51 $bAsKML = (boolean)isset($_GET['polygon_kml']) && $_GET['polygon_kml'];
52 $bAsSVG = (boolean)isset($_GET['polygon_svg']) && $_GET['polygon_svg'];
53 $bAsText = (boolean)isset($_GET['polygon_text']) && $_GET['polygon_text'];
54 if ((($bShowPolygons?1:0)
59 ) > CONST_PolygonOutput_MaximumTypes)
61 if (CONST_PolygonOutput_MaximumTypes)
63 userError("Select only ".CONST_PolygonOutput_MaximumTypes." polgyon output option");
67 userError("Polygon output is disabled");
73 // Show address breakdown
74 $bShowAddressDetails = isset($_GET['addressdetails']) && $_GET['addressdetails'];
77 $aLangPrefOrder = getPreferredLanguages();
78 $bReverseInPlan = true;
80 if (isset($aLangPrefOrder['name:de'])) $bReverseInPlan = true;
81 if (isset($aLangPrefOrder['name:ru'])) $bReverseInPlan = true;
82 if (isset($aLangPrefOrder['name:ja'])) $bReverseInPlan = true;
83 if (isset($aLangPrefOrder['name:pl'])) $bReverseInPlan = true;
86 $sLanguagePrefArraySQL = "ARRAY[".join(',',array_map("getDBQuoted",$aLangPrefOrder))."]";
88 if (isset($_GET['exclude_place_ids']) && $_GET['exclude_place_ids'])
90 foreach(explode(',',$_GET['exclude_place_ids']) as $iExcludedPlaceID)
92 $iExcludedPlaceID = (int)$iExcludedPlaceID;
93 if ($iExcludedPlaceID) $aExcludePlaceIDs[$iExcludedPlaceID] = $iExcludedPlaceID;
97 // Only certain ranks of feature
98 if (isset($_GET['featureType']) && !isset($_GET['featuretype'])) $_GET['featuretype'] = $_GET['featureType'];
100 if (isset($_GET['featuretype']))
102 switch($_GET['featuretype'])
105 $iMinAddressRank = $iMaxAddressRank = 4;
108 $iMinAddressRank = $iMaxAddressRank = 8;
111 $iMinAddressRank = 14;
112 $iMaxAddressRank = 16;
115 $iMinAddressRank = 8;
116 $iMaxAddressRank = 20;
121 if (isset($_GET['countrycodes']))
123 $aCountryCodes = array();
124 foreach(explode(',',$_GET['countrycodes']) as $sCountryCode)
126 if (preg_match('/^[a-zA-Z][a-zA-Z]$/', $sCountryCode))
128 $aCountryCodes[] = "'".strtolower($sCountryCode)."'";
131 $sCountryCodesSQL = join(',', $aCountryCodes);
135 $sQuery = (isset($_GET['q'])?trim($_GET['q']):'');
136 if (!$sQuery && isset($_SERVER['PATH_INFO']) && $_SERVER['PATH_INFO'][0] == '/')
138 $sQuery = substr($_SERVER['PATH_INFO'], 1);
140 // reverse order of '/' separated string
141 $aPhrases = explode('/', $sQuery);
142 $aPhrases = array_reverse($aPhrases);
143 $sQuery = join(', ',$aPhrases);
147 $aStructuredOptions = array(
148 array('amenity', 26, 30, false),
149 array('street', 26, 30, false),
150 array('city', 14, 24, false),
151 array('county', 9, 13, false),
152 array('state', 8, 8, false),
153 array('country', 4, 4, false),
154 array('postalcode', 5, 11, array(5, 11)),
156 $aStructuredQuery = array();
157 $sAllowedTypesSQLList = '';
158 foreach($aStructuredOptions as $aStructuredOption)
160 loadStructuredAddressElement($aStructuredQuery, $iMinAddressRank, $iMaxAddressRank, $aAddressRankList, $_GET, $aStructuredOption[0], $aStructuredOption[1], $aStructuredOption[2], $aStructuredOption[3]);
162 if (sizeof($aStructuredQuery) > 0)
164 $sQuery = join(', ', $aStructuredQuery);
165 if ($iMaxAddressRank < 30)
167 $sAllowedTypesSQLList = '(\'place\',\'boundary\')';
173 $hLog = logStart($oDB, 'search', $sQuery, $aLangPrefOrder);
175 // Hack to make it handle "new york, ny" (and variants) correctly
176 $sQuery = str_ireplace(array('New York, ny','new york, new york', 'New York ny','new york new york'), 'new york city, ny', $sQuery);
177 if (isset($aLangPrefOrder['name:en']))
179 $sQuery = preg_replace('/,\s*il\s*(,|$)/',', illinois\1', $sQuery);
180 $sQuery = preg_replace('/,\s*al\s*(,|$)/',', alabama\1', $sQuery);
181 $sQuery = preg_replace('/,\s*la\s*(,|$)/',', louisiana\1', $sQuery);
184 // If we have a view box create the SQL
185 // Small is the actual view box, Large is double (on each axis) that
186 $sViewboxCentreSQL = $sViewboxSmallSQL = $sViewboxLargeSQL = false;
187 if (isset($_GET['viewboxlbrt']) && $_GET['viewboxlbrt'])
189 $aCoOrdinatesLBRT = explode(',',$_GET['viewboxlbrt']);
190 $_GET['viewbox'] = $aCoOrdinatesLBRT[0].','.$aCoOrdinatesLBRT[3].','.$aCoOrdinatesLBRT[2].','.$aCoOrdinatesLBRT[1];
192 if (isset($_GET['viewbox']) && $_GET['viewbox'])
194 $aCoOrdinates = explode(',',$_GET['viewbox']);
195 $sViewboxSmallSQL = "ST_SetSRID(ST_MakeBox2D(ST_Point(".(float)$aCoOrdinates[0].",".(float)$aCoOrdinates[1]."),ST_Point(".(float)$aCoOrdinates[2].",".(float)$aCoOrdinates[3].")),4326)";
196 $fHeight = $aCoOrdinates[0]-$aCoOrdinates[2];
197 $fWidth = $aCoOrdinates[1]-$aCoOrdinates[3];
198 $aCoOrdinates[0] += $fHeight;
199 $aCoOrdinates[2] -= $fHeight;
200 $aCoOrdinates[1] += $fWidth;
201 $aCoOrdinates[3] -= $fWidth;
202 $sViewboxLargeSQL = "ST_SetSRID(ST_MakeBox2D(ST_Point(".(float)$aCoOrdinates[0].",".(float)$aCoOrdinates[1]."),ST_Point(".(float)$aCoOrdinates[2].",".(float)$aCoOrdinates[3].")),4326)";
206 $bBoundingBoxSearch = false;
208 if (isset($_GET['route']) && $_GET['route'] && isset($_GET['routewidth']) && $_GET['routewidth'])
210 $aPoints = explode(',',$_GET['route']);
211 if (sizeof($aPoints) % 2 != 0)
213 userError("Uneven number of points");
216 $sViewboxCentreSQL = "ST_SetSRID('LINESTRING(";
218 foreach($aPoints as $i => $fPoint)
222 if ($i != 1) $sViewboxCentreSQL .= ",";
223 $sViewboxCentreSQL .= ((float)$fPoint).' '.$fPrevCoord;
227 $fPrevCoord = (float)$fPoint;
230 $sViewboxCentreSQL .= ")'::geometry,4326)";
232 $sSQL = "select st_buffer(".$sViewboxCentreSQL.",".(float)($_GET['routewidth']/69).")";
233 $sViewboxSmallSQL = $oDB->getOne($sSQL);
234 if (PEAR::isError($sViewboxSmallSQL))
236 failInternalError("Could not get small viewbox.", $sSQL, $sViewboxSmallSQL);
238 $sViewboxSmallSQL = "'".$sViewboxSmallSQL."'::geometry";
240 $sSQL = "select st_buffer(".$sViewboxCentreSQL.",".(float)($_GET['routewidth']/30).")";
241 $sViewboxLargeSQL = $oDB->getOne($sSQL);
242 if (PEAR::isError($sViewboxLargeSQL))
244 failInternalError("Could not get large viewbox.", $sSQL, $sViewboxLargeSQL);
246 $sViewboxLargeSQL = "'".$sViewboxLargeSQL."'::geometry";
247 $bBoundingBoxSearch = true;
250 // Do we have anything that looks like a lat/lon pair?
251 if (preg_match('/\\b([NS])[ ]+([0-9]+[0-9.]*)[ ]+([0-9.]+)?[, ]+([EW])[ ]+([0-9]+)[ ]+([0-9]+[0-9.]*)?\\b/', $sQuery, $aData))
253 $fQueryLat = ($aData[1]=='N'?1:-1) * ($aData[2] + $aData[3]/60);
254 $fQueryLon = ($aData[4]=='E'?1:-1) * ($aData[5] + $aData[6]/60);
255 if ($fQueryLat <= 90.1 && $fQueryLat >= -90.1 && $fQueryLon <= 180.1 && $fQueryLon >= -180.1)
257 $_GET['nearlat'] = $fQueryLat;
258 $_GET['nearlon'] = $fQueryLon;
259 $sQuery = trim(str_replace($aData[0], ' ', $sQuery));
262 elseif (preg_match('/\\b([0-9]+)[ ]+([0-9]+[0-9.]*)?[ ]+([NS])[, ]+([0-9]+)[ ]+([0-9]+[0-9.]*)?[ ]+([EW])\\b/', $sQuery, $aData))
264 $fQueryLat = ($aData[3]=='N'?1:-1) * ($aData[1] + $aData[2]/60);
265 $fQueryLon = ($aData[6]=='E'?1:-1) * ($aData[4] + $aData[5]/60);
266 if ($fQueryLat <= 90.1 && $fQueryLat >= -90.1 && $fQueryLon <= 180.1 && $fQueryLon >= -180.1)
268 $_GET['nearlat'] = $fQueryLat;
269 $_GET['nearlon'] = $fQueryLon;
270 $sQuery = trim(str_replace($aData[0], ' ', $sQuery));
273 elseif (preg_match('/(\\[|^|\\b)(-?[0-9]+[0-9.]*)[, ]+(-?[0-9]+[0-9.]*)(\\]|$|\\b)/', $sQuery, $aData))
275 $fQueryLat = $aData[2];
276 $fQueryLon = $aData[3];
277 if ($fQueryLat <= 90.1 && $fQueryLat >= -90.1 && $fQueryLon <= 180.1 && $fQueryLon >= -180.1)
279 $_GET['nearlat'] = $fQueryLat;
280 $_GET['nearlon'] = $fQueryLon;
281 $sQuery = trim(str_replace($aData[0], ' ', $sQuery));
285 if ($sQuery || $aStructuredQuery)
287 // Start with a blank search
289 array('iSearchRank' => 0, 'iNamePhrase' => -1, 'sCountryCode' => false, 'aName'=>array(), 'aAddress'=>array(), 'aFullNameAddress'=>array(),
290 'aNameNonSearch'=>array(), 'aAddressNonSearch'=>array(),
291 'sOperator'=>'', 'aFeatureName' => array(), 'sClass'=>'', 'sType'=>'', 'sHouseNumber'=>'', 'fLat'=>'', 'fLon'=>'', 'fRadius'=>'')
294 $sNearPointSQL = false;
295 if (isset($_GET['nearlat']) && isset($_GET['nearlon']))
297 $sNearPointSQL = "ST_SetSRID(ST_Point(".(float)$_GET['nearlon'].",".(float)$_GET['nearlat']."),4326)";
298 $aSearches[0]['fLat'] = (float)$_GET['nearlat'];
299 $aSearches[0]['fLon'] = (float)$_GET['nearlon'];
300 $aSearches[0]['fRadius'] = 0.1;
303 $bSpecialTerms = false;
304 preg_match_all('/\\[(.*)=(.*)\\]/', $sQuery, $aSpecialTermsRaw, PREG_SET_ORDER);
305 $aSpecialTerms = array();
306 foreach($aSpecialTermsRaw as $aSpecialTerm)
308 $sQuery = str_replace($aSpecialTerm[0], ' ', $sQuery);
309 $aSpecialTerms[strtolower($aSpecialTerm[1])] = $aSpecialTerm[2];
312 preg_match_all('/\\[([\\w ]*)\\]/u', $sQuery, $aSpecialTermsRaw, PREG_SET_ORDER);
313 $aSpecialTerms = array();
314 if (isset($aStructuredQuery['amenity']) && $aStructuredQuery['amenity'])
316 $aSpecialTermsRaw[] = array('['.$aStructuredQuery['amenity'].']', $aStructuredQuery['amenity']);
317 unset($aStructuredQuery['amenity']);
319 foreach($aSpecialTermsRaw as $aSpecialTerm)
321 $sQuery = str_replace($aSpecialTerm[0], ' ', $sQuery);
322 $sToken = $oDB->getOne("select make_standard_name('".$aSpecialTerm[1]."') as string");
323 $sSQL = 'select * from (select word_id,word_token, word, class, type, country_code, operator';
324 $sSQL .= ' from word where word_token in (\' '.$sToken.'\')) as x where (class is not null and class not in (\'place\')) or country_code is not null';
325 if (CONST_Debug) var_Dump($sSQL);
326 $aSearchWords = $oDB->getAll($sSQL);
327 $aNewSearches = array();
328 foreach($aSearches as $aSearch)
330 foreach($aSearchWords as $aSearchTerm)
332 $aNewSearch = $aSearch;
333 if ($aSearchTerm['country_code'])
335 $aNewSearch['sCountryCode'] = strtolower($aSearchTerm['country_code']);
336 $aNewSearches[] = $aNewSearch;
337 $bSpecialTerms = true;
339 if ($aSearchTerm['class'])
341 $aNewSearch['sClass'] = $aSearchTerm['class'];
342 $aNewSearch['sType'] = $aSearchTerm['type'];
343 $aNewSearches[] = $aNewSearch;
344 $bSpecialTerms = true;
348 $aSearches = $aNewSearches;
351 // Split query into phrases
352 // Commas are used to reduce the search space by indicating where phrases split
353 if (sizeof($aStructuredQuery) > 0)
355 $aPhrases = $aStructuredQuery;
356 $bStructuredPhrases = true;
360 $aPhrases = explode(',',$sQuery);
361 $bStructuredPhrases = false;
364 // Convert each phrase to standard form
365 // Create a list of standard words
366 // Get all 'sets' of words
367 // Generate a complete list of all
369 foreach($aPhrases as $iPhrase => $sPhrase)
371 $aPhrase = $oDB->getRow("select make_standard_name('".pg_escape_string($sPhrase)."') as string");
372 if (PEAR::isError($aPhrase))
374 userError("Illegal query string (not an UTF-8 string): ".$sPhrase);
375 if (CONST_Debug) var_dump($aPhrase);
378 if (trim($aPhrase['string']))
380 $aPhrases[$iPhrase] = $aPhrase;
381 $aPhrases[$iPhrase]['words'] = explode(' ',$aPhrases[$iPhrase]['string']);
382 $aPhrases[$iPhrase]['wordsets'] = getWordSets($aPhrases[$iPhrase]['words'], 0);
383 $aTokens = array_merge($aTokens, getTokensFromSets($aPhrases[$iPhrase]['wordsets']));
387 unset($aPhrases[$iPhrase]);
391 // reindex phrases - we make assumptions later on
392 $aPhraseTypes = array_keys($aPhrases);
393 $aPhrases = array_values($aPhrases);
395 if (sizeof($aTokens))
398 // Check which tokens we have, get the ID numbers
399 $sSQL = 'select word_id,word_token, word, class, type, country_code, operator, search_name_count';
400 $sSQL .= ' from word where word_token in ('.join(',',array_map("getDBQuoted",$aTokens)).')';
401 //$sSQL .= ' and search_name_count < '.CONST_Max_Word_Frequency;
402 //$sSQL .= ' group by word_token, word, class, type, country_code';
404 if (CONST_Debug) var_Dump($sSQL);
406 $aValidTokens = array();
407 if (sizeof($aTokens)) $aDatabaseWords = $oDB->getAll($sSQL);
408 else $aDatabaseWords = array();
409 if (PEAR::IsError($aDatabaseWords))
411 failInternalError("Could not get word tokens.", $sSQL, $aDatabaseWords);
413 $aPossibleMainWordIDs = array();
414 $aWordFrequencyScores = array();
415 foreach($aDatabaseWords as $aToken)
417 // Very special case - require 2 letter country param to match the country code found
418 if ($bStructuredPhrases && $aToken['country_code'] && !empty($aStructuredQuery['country'])
419 && strlen($aStructuredQuery['country']) == 2 && strtolower($aStructuredQuery['country']) != $aToken['country_code'])
424 if (isset($aValidTokens[$aToken['word_token']]))
426 $aValidTokens[$aToken['word_token']][] = $aToken;
430 $aValidTokens[$aToken['word_token']] = array($aToken);
432 if (!$aToken['class'] && !$aToken['country_code']) $aPossibleMainWordIDs[$aToken['word_id']] = 1;
433 $aWordFrequencyScores[$aToken['word_id']] = $aToken['search_name_count'] + 1;
435 if (CONST_Debug) var_Dump($aPhrases, $aValidTokens);
437 // Try and calculate GB postcodes we might be missing
438 foreach($aTokens as $sToken)
440 // Source of gb postcodes is now definitive - always use
441 if (preg_match('/^([A-Z][A-Z]?[0-9][0-9A-Z]? ?[0-9])([A-Z][A-Z])$/', strtoupper(trim($sToken)), $aData))
443 if (substr($aData[1],-2,1) != ' ')
445 $aData[0] = substr($aData[0],0,strlen($aData[1]-1)).' '.substr($aData[0],strlen($aData[1]-1));
446 $aData[1] = substr($aData[1],0,-1).' '.substr($aData[1],-1,1);
448 $aGBPostcodeLocation = gbPostcodeCalculate($aData[0], $aData[1], $aData[2], $oDB);
449 if ($aGBPostcodeLocation)
451 $aValidTokens[$sToken] = $aGBPostcodeLocation;
456 foreach($aTokens as $sToken)
458 // Unknown single word token with a number - assume it is a house number
459 if (!isset($aValidTokens[' '.$sToken]) && strpos($sToken,' ') === false && preg_match('/[0-9]/', $sToken))
461 $aValidTokens[' '.$sToken] = array(array('class'=>'place','type'=>'house'));
465 // Any words that have failed completely?
468 // Start the search process
469 $aResultPlaceIDs = array();
472 Calculate all searches using aValidTokens i.e.
473 'Wodsworth Road, Sheffield' =>
477 0 1 (wodsworth)(road)
480 Score how good the search is so they can be ordered
482 foreach($aPhrases as $iPhrase => $sPhrase)
484 $aNewPhraseSearches = array();
485 if ($bStructuredPhrases) $sPhraseType = $aPhraseTypes[$iPhrase];
486 else $sPhraseType = '';
488 foreach($aPhrases[$iPhrase]['wordsets'] as $aWordset)
490 $aWordsetSearches = $aSearches;
492 // Add all words from this wordset
493 foreach($aWordset as $iToken => $sToken)
495 //echo "<br><b>$sToken</b>";
496 $aNewWordsetSearches = array();
498 foreach($aWordsetSearches as $aCurrentSearch)
501 //var_dump($aCurrentSearch);
504 // If the token is valid
505 if (isset($aValidTokens[' '.$sToken]))
507 foreach($aValidTokens[' '.$sToken] as $aSearchTerm)
509 $aSearch = $aCurrentSearch;
510 $aSearch['iSearchRank']++;
511 if (($sPhraseType == '' || $sPhraseType == 'country') && !empty($aSearchTerm['country_code']) && $aSearchTerm['country_code'] != '0')
513 if ($aSearch['sCountryCode'] === false)
515 $aSearch['sCountryCode'] = strtolower($aSearchTerm['country_code']);
516 // Country is almost always at the end of the string - increase score for finding it anywhere else (optimisation)
517 // If reverse order is enabled, it may appear at the beginning as well.
518 if (($iToken+1 != sizeof($aWordset) || $iPhrase+1 != sizeof($aPhrases)) &&
519 (!$bReverseInPlan || $iToken > 0 || $iPhrase > 0))
521 $aSearch['iSearchRank'] += 5;
523 if ($aSearch['iSearchRank'] < $iMaxRank) $aNewWordsetSearches[] = $aSearch;
526 elseif (isset($aSearchTerm['lat']) && $aSearchTerm['lat'] !== '' && $aSearchTerm['lat'] !== null)
528 if ($aSearch['fLat'] === '')
530 $aSearch['fLat'] = $aSearchTerm['lat'];
531 $aSearch['fLon'] = $aSearchTerm['lon'];
532 $aSearch['fRadius'] = $aSearchTerm['radius'];
533 if ($aSearch['iSearchRank'] < $iMaxRank) $aNewWordsetSearches[] = $aSearch;
536 elseif ($sPhraseType == 'postalcode')
538 // 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
539 if (isset($aSearchTerm['word_id']) && $aSearchTerm['word_id'])
541 // If we already have a name try putting the postcode first
542 if (sizeof($aSearch['aName']))
544 $aNewSearch = $aSearch;
545 $aNewSearch['aAddress'] = array_merge($aNewSearch['aAddress'], $aNewSearch['aName']);
546 $aNewSearch['aName'] = array();
547 $aNewSearch['aName'][$aSearchTerm['word_id']] = $aSearchTerm['word_id'];
548 if ($aSearch['iSearchRank'] < $iMaxRank) $aNewWordsetSearches[] = $aNewSearch;
551 if (sizeof($aSearch['aName']))
553 if ((!$bStructuredPhrases || $iPhrase > 0) && $sPhraseType != 'country' && (!isset($aValidTokens[$sToken]) || strlen($sToken) < 4 || strpos($sToken, ' ') !== false))
555 $aSearch['aAddress'][$aSearchTerm['word_id']] = $aSearchTerm['word_id'];
559 $aCurrentSearch['aFullNameAddress'][$aSearchTerm['word_id']] = $aSearchTerm['word_id'];
560 $aSearch['iSearchRank'] += 1000; // skip;
565 $aSearch['aName'][$aSearchTerm['word_id']] = $aSearchTerm['word_id'];
566 //$aSearch['iNamePhrase'] = $iPhrase;
568 if ($aSearch['iSearchRank'] < $iMaxRank) $aNewWordsetSearches[] = $aSearch;
572 elseif (($sPhraseType == '' || $sPhraseType == 'street') && $aSearchTerm['class'] == 'place' && $aSearchTerm['type'] == 'house')
574 if ($aSearch['sHouseNumber'] === '')
576 $aSearch['sHouseNumber'] = $sToken;
577 if ($aSearch['iSearchRank'] < $iMaxRank) $aNewWordsetSearches[] = $aSearch;
579 // Fall back to not searching for this item (better than nothing)
580 $aSearch = $aCurrentSearch;
581 $aSearch['iSearchRank'] += 1;
582 if ($aSearch['iSearchRank'] < $iMaxRank) $aNewWordsetSearches[] = $aSearch;
586 elseif ($sPhraseType == '' && $aSearchTerm['class'] !== '' && $aSearchTerm['class'] !== null)
588 if ($aSearch['sClass'] === '')
590 $aSearch['sOperator'] = $aSearchTerm['operator'];
591 $aSearch['sClass'] = $aSearchTerm['class'];
592 $aSearch['sType'] = $aSearchTerm['type'];
593 if (sizeof($aSearch['aName'])) $aSearch['sOperator'] = 'name';
594 else $aSearch['sOperator'] = 'near'; // near = in for the moment
596 // Do we have a shortcut id?
597 if ($aSearch['sOperator'] == 'name')
599 $sSQL = "select get_tagpair('".$aSearch['sClass']."', '".$aSearch['sType']."')";
600 if ($iAmenityID = $oDB->getOne($sSQL))
602 $aValidTokens[$aSearch['sClass'].':'.$aSearch['sType']] = array('word_id' => $iAmenityID);
603 $aSearch['aName'][$iAmenityID] = $iAmenityID;
604 $aSearch['sClass'] = '';
605 $aSearch['sType'] = '';
608 if ($aSearch['iSearchRank'] < $iMaxRank) $aNewWordsetSearches[] = $aSearch;
611 elseif (isset($aSearchTerm['word_id']) && $aSearchTerm['word_id'])
613 if (sizeof($aSearch['aName']))
615 if ((!$bStructuredPhrases || $iPhrase > 0) && $sPhraseType != 'country' && (!isset($aValidTokens[$sToken]) || strlen($sToken) < 4 || strpos($sToken, ' ') !== false))
617 $aSearch['aAddress'][$aSearchTerm['word_id']] = $aSearchTerm['word_id'];
621 $aCurrentSearch['aFullNameAddress'][$aSearchTerm['word_id']] = $aSearchTerm['word_id'];
622 $aSearch['iSearchRank'] += 1000; // skip;
627 $aSearch['aName'][$aSearchTerm['word_id']] = $aSearchTerm['word_id'];
628 //$aSearch['iNamePhrase'] = $iPhrase;
630 if ($aSearch['iSearchRank'] < $iMaxRank) $aNewWordsetSearches[] = $aSearch;
634 if (isset($aValidTokens[$sToken]))
636 // Allow searching for a word - but at extra cost
637 foreach($aValidTokens[$sToken] as $aSearchTerm)
639 if (isset($aSearchTerm['word_id']) && $aSearchTerm['word_id'])
641 if ((!$bStructuredPhrases || $iPhrase > 0) && sizeof($aCurrentSearch['aName']) && strlen($sToken) >= 4)
643 $aSearch = $aCurrentSearch;
644 $aSearch['iSearchRank'] += 1;
645 if ($aWordFrequencyScores[$aSearchTerm['word_id']] < CONST_Max_Word_Frequency)
647 $aSearch['aAddress'][$aSearchTerm['word_id']] = $aSearchTerm['word_id'];
648 if ($aSearch['iSearchRank'] < $iMaxRank) $aNewWordsetSearches[] = $aSearch;
650 elseif (isset($aValidTokens[' '.$sToken])) // revert to the token version?
652 foreach($aValidTokens[' '.$sToken] as $aSearchTermToken)
654 if (empty($aSearchTermToken['country_code'])
655 && empty($aSearchTermToken['lat'])
656 && empty($aSearchTermToken['class']))
658 $aSearch = $aCurrentSearch;
659 $aSearch['iSearchRank'] += 1;
660 $aSearch['aAddress'][$aSearchTermToken['word_id']] = $aSearchTermToken['word_id'];
661 if ($aSearch['iSearchRank'] < $iMaxRank) $aNewWordsetSearches[] = $aSearch;
667 $aSearch['aAddressNonSearch'][$aSearchTerm['word_id']] = $aSearchTerm['word_id'];
668 if ($aSearch['iSearchRank'] < $iMaxRank) $aNewWordsetSearches[] = $aSearch;
672 if (!sizeof($aCurrentSearch['aName']) || $aCurrentSearch['iNamePhrase'] == $iPhrase)
674 $aSearch = $aCurrentSearch;
675 $aSearch['iSearchRank'] += 2;
676 if (preg_match('#^[0-9]+$#', $sToken)) $aSearch['iSearchRank'] += 2;
677 if ($aWordFrequencyScores[$aSearchTerm['word_id']] < CONST_Max_Word_Frequency)
678 $aSearch['aName'][$aSearchTerm['word_id']] = $aSearchTerm['word_id'];
680 $aSearch['aNameNonSearch'][$aSearchTerm['word_id']] = $aSearchTerm['word_id'];
681 $aSearch['iNamePhrase'] = $iPhrase;
682 if ($aSearch['iSearchRank'] < $iMaxRank) $aNewWordsetSearches[] = $aSearch;
689 // Allow skipping a word - but at EXTREAM cost
690 //$aSearch = $aCurrentSearch;
691 //$aSearch['iSearchRank']+=100;
692 //$aNewWordsetSearches[] = $aSearch;
696 usort($aNewWordsetSearches, 'bySearchRank');
697 $aWordsetSearches = array_slice($aNewWordsetSearches, 0, 50);
699 //var_Dump('<hr>',sizeof($aWordsetSearches)); exit;
701 $aNewPhraseSearches = array_merge($aNewPhraseSearches, $aNewWordsetSearches);
702 usort($aNewPhraseSearches, 'bySearchRank');
704 $aSearchHash = array();
705 foreach($aNewPhraseSearches as $iSearch => $aSearch)
707 $sHash = serialize($aSearch);
708 if (isset($aSearchHash[$sHash])) unset($aNewPhraseSearches[$iSearch]);
709 else $aSearchHash[$sHash] = 1;
712 $aNewPhraseSearches = array_slice($aNewPhraseSearches, 0, 50);
715 // Re-group the searches by their score, junk anything over 20 as just not worth trying
716 $aGroupedSearches = array();
717 foreach($aNewPhraseSearches as $aSearch)
719 if ($aSearch['iSearchRank'] < $iMaxRank)
721 if (!isset($aGroupedSearches[$aSearch['iSearchRank']])) $aGroupedSearches[$aSearch['iSearchRank']] = array();
722 $aGroupedSearches[$aSearch['iSearchRank']][] = $aSearch;
725 ksort($aGroupedSearches);
728 $aSearches = array();
729 foreach($aGroupedSearches as $iScore => $aNewSearches)
731 $iSearchCount += sizeof($aNewSearches);
732 $aSearches = array_merge($aSearches, $aNewSearches);
733 if ($iSearchCount > 50) break;
736 //if (CONST_Debug) _debugDumpGroupedSearches($aGroupedSearches, $aValidTokens);
743 // Re-group the searches by their score, junk anything over 20 as just not worth trying
744 $aGroupedSearches = array();
745 foreach($aSearches as $aSearch)
747 if ($aSearch['iSearchRank'] < $iMaxRank)
749 if (!isset($aGroupedSearches[$aSearch['iSearchRank']])) $aGroupedSearches[$aSearch['iSearchRank']] = array();
750 $aGroupedSearches[$aSearch['iSearchRank']][] = $aSearch;
753 ksort($aGroupedSearches);
756 if (CONST_Debug) var_Dump($aGroupedSearches);
760 $aCopyGroupedSearches = $aGroupedSearches;
761 foreach($aCopyGroupedSearches as $iGroup => $aSearches)
763 foreach($aSearches as $iSearch => $aSearch)
765 if (sizeof($aSearch['aAddress']))
767 $iReverseItem = array_pop($aSearch['aAddress']);
768 if (isset($aPossibleMainWordIDs[$iReverseItem]))
770 $aSearch['aAddress'] = array_merge($aSearch['aAddress'], $aSearch['aName']);
771 $aSearch['aName'] = array($iReverseItem);
772 $aGroupedSearches[$iGroup][] = $aSearch;
774 //$aReverseSearch['aName'][$iReverseItem] = $iReverseItem;
775 //$aGroupedSearches[$iGroup][] = $aReverseSearch;
781 if (CONST_Search_TryDroppedAddressTerms && sizeof($aStructuredQuery) > 0)
783 $aCopyGroupedSearches = $aGroupedSearches;
784 foreach($aCopyGroupedSearches as $iGroup => $aSearches)
786 foreach($aSearches as $iSearch => $aSearch)
788 $aReductionsList = array($aSearch['aAddress']);
789 $iSearchRank = $aSearch['iSearchRank'];
790 while(sizeof($aReductionsList) > 0)
793 if ($iSearchRank > iMaxRank) break 3;
794 $aNewReductionsList = array();
795 foreach($aReductionsList as $aReductionsWordList)
797 for ($iReductionWord = 0; $iReductionWord < sizeof($aReductionsWordList); $iReductionWord++)
799 $aReductionsWordListResult = array_merge(array_slice($aReductionsWordList, 0, $iReductionWord), array_slice($aReductionsWordList, $iReductionWord+1));
800 $aReverseSearch = $aSearch;
801 $aSearch['aAddress'] = $aReductionsWordListResult;
802 $aSearch['iSearchRank'] = $iSearchRank;
803 $aGroupedSearches[$iSearchRank][] = $aReverseSearch;
804 if (sizeof($aReductionsWordListResult) > 0)
806 $aNewReductionsList[] = $aReductionsWordListResult;
810 $aReductionsList = $aNewReductionsList;
814 ksort($aGroupedSearches);
817 // Filter out duplicate searches
818 $aSearchHash = array();
819 foreach($aGroupedSearches as $iGroup => $aSearches)
821 foreach($aSearches as $iSearch => $aSearch)
823 $sHash = serialize($aSearch);
824 if (isset($aSearchHash[$sHash]))
826 unset($aGroupedSearches[$iGroup][$iSearch]);
827 if (sizeof($aGroupedSearches[$iGroup]) == 0) unset($aGroupedSearches[$iGroup]);
831 $aSearchHash[$sHash] = 1;
836 if (CONST_Debug) _debugDumpGroupedSearches($aGroupedSearches, $aValidTokens);
840 foreach($aGroupedSearches as $iGroupedRank => $aSearches)
843 foreach($aSearches as $aSearch)
847 if (CONST_Debug) { echo "<hr><b>Search Loop, group $iGroupLoop, loop $iQueryLoop</b>"; }
848 if (CONST_Debug) _debugDumpGroupedSearches(array($iGroupedRank => array($aSearch)), $aValidTokens);
851 // Must have a location term
852 if (!sizeof($aSearch['aName']) && !sizeof($aSearch['aAddress']) && !$aSearch['fLon'])
854 if ($aSearch['sCountryCode'] && !$aSearch['sClass'] && !$aSearch['sHouseNumber'])
856 if (4 >= $iMinAddressRank && 4 <= $iMaxAddressRank)
858 $sSQL = "select place_id from placex where calculated_country_code='".$aSearch['sCountryCode']."' and rank_search = 4";
859 if ($sCountryCodesSQL) $sSQL .= " and calculated_country_code in ($sCountryCodesSQL)";
860 $sSQL .= " order by st_area(geometry) desc limit 1";
861 if (CONST_Debug) var_dump($sSQL);
862 $aPlaceIDs = $oDB->getCol($sSQL);
867 if (!$bBoundingBoxSearch && !$aSearch['fLon']) continue;
868 if (!$aSearch['sClass']) continue;
869 $sSQL = "select count(*) from pg_tables where tablename = 'place_classtype_".$aSearch['sClass']."_".$aSearch['sType']."'";
870 if ($oDB->getOne($sSQL))
872 $sSQL = "select place_id from place_classtype_".$aSearch['sClass']."_".$aSearch['sType']." ct";
873 if ($sCountryCodesSQL) $sSQL .= " join placex using (place_id)";
874 $sSQL .= " where st_contains($sViewboxSmallSQL, ct.centroid)";
875 if ($sCountryCodesSQL) $sSQL .= " and calculated_country_code in ($sCountryCodesSQL)";
876 if (sizeof($aExcludePlaceIDs))
878 $sSQL .= " and place_id not in (".join(',',$aExcludePlaceIDs).")";
880 if ($sViewboxCentreSQL) $sSQL .= " order by st_distance($sViewboxCentreSQL, ct.centroid) asc";
881 $sSQL .= " limit $iLimit";
882 if (CONST_Debug) var_dump($sSQL);
883 $aPlaceIDs = $oDB->getCol($sSQL);
885 // If excluded place IDs are given, it is fair to assume that
886 // there have been results in the small box, so no further
887 // expansion in that case.
888 if (!sizeof($aPlaceIDs) && !sizeof($aExcludePlaceIDs))
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($sViewboxLargeSQL, ct.centroid)";
893 if ($sCountryCodesSQL) $sSQL .= " and calculated_country_code in ($sCountryCodesSQL)";
894 if ($sViewboxCentreSQL) $sSQL .= " order by st_distance($sViewboxCentreSQL, ct.centroid) asc";
895 $sSQL .= " limit $iLimit";
896 if (CONST_Debug) var_dump($sSQL);
897 $aPlaceIDs = $oDB->getCol($sSQL);
902 $sSQL = "select place_id from placex where class='".$aSearch['sClass']."' and type='".$aSearch['sType']."'";
903 $sSQL .= " and st_contains($sViewboxSmallSQL, geometry) and linked_place_id is null";
904 if ($sCountryCodesSQL) $sSQL .= " and calculated_country_code in ($sCountryCodesSQL)";
905 if ($sViewboxCentreSQL) $sSQL .= " order by st_distance($sViewboxCentreSQL, centroid) asc";
906 $sSQL .= " limit $iLimit";
907 if (CONST_Debug) var_dump($sSQL);
908 $aPlaceIDs = $oDB->getCol($sSQL);
914 $aPlaceIDs = array();
916 // First we need a position, either aName or fLat or both
920 // TODO: filter out the pointless search terms (2 letter name tokens and less)
921 // they might be right - but they are just too darned expensive to run
922 if (sizeof($aSearch['aName'])) $aTerms[] = "name_vector @> ARRAY[".join($aSearch['aName'],",")."]";
923 if (sizeof($aSearch['aNameNonSearch'])) $aTerms[] = "array_cat(name_vector,ARRAY[]::integer[]) @> ARRAY[".join($aSearch['aNameNonSearch'],",")."]";
924 if (sizeof($aSearch['aAddress']) && $aSearch['aName'] != $aSearch['aAddress'])
926 // For infrequent name terms disable index usage for address
927 if (CONST_Search_NameOnlySearchFrequencyThreshold &&
928 sizeof($aSearch['aName']) == 1 &&
929 $aWordFrequencyScores[$aSearch['aName'][reset($aSearch['aName'])]] < CONST_Search_NameOnlySearchFrequencyThreshold)
931 $aTerms[] = "array_cat(nameaddress_vector,ARRAY[]::integer[]) @> ARRAY[".join(array_merge($aSearch['aAddress'],$aSearch['aAddressNonSearch']),",")."]";
935 $aTerms[] = "nameaddress_vector @> ARRAY[".join($aSearch['aAddress'],",")."]";
936 if (sizeof($aSearch['aAddressNonSearch'])) $aTerms[] = "array_cat(nameaddress_vector,ARRAY[]::integer[]) @> ARRAY[".join($aSearch['aAddressNonSearch'],",")."]";
939 if ($aSearch['sCountryCode']) $aTerms[] = "country_code = '".pg_escape_string($aSearch['sCountryCode'])."'";
940 if ($aSearch['sHouseNumber']) $aTerms[] = "address_rank between 16 and 27";
941 if ($aSearch['fLon'] && $aSearch['fLat'])
943 $aTerms[] = "ST_DWithin(centroid, ST_SetSRID(ST_Point(".$aSearch['fLon'].",".$aSearch['fLat']."),4326), ".$aSearch['fRadius'].")";
944 $aOrder[] = "ST_Distance(centroid, ST_SetSRID(ST_Point(".$aSearch['fLon'].",".$aSearch['fLat']."),4326)) ASC";
946 if (sizeof($aExcludePlaceIDs))
948 $aTerms[] = "place_id not in (".join(',',$aExcludePlaceIDs).")";
950 if ($sCountryCodesSQL)
952 $aTerms[] = "country_code in ($sCountryCodesSQL)";
955 if ($bBoundingBoxSearch) $aTerms[] = "centroid && $sViewboxSmallSQL";
956 if ($sNearPointSQL) $aOrder[] = "ST_Distance($sNearPointSQL, centroid) asc";
958 $sImportanceSQL = '(case when importance = 0 OR importance IS NULL then 0.75-(search_rank::float/40) else importance end)';
959 if ($sViewboxSmallSQL) $sImportanceSQL .= " * case when ST_Contains($sViewboxSmallSQL, centroid) THEN 1 ELSE 0.5 END";
960 if ($sViewboxLargeSQL) $sImportanceSQL .= " * case when ST_Contains($sViewboxLargeSQL, centroid) THEN 1 ELSE 0.5 END";
961 $aOrder[] = "$sImportanceSQL DESC";
962 if (sizeof($aSearch['aFullNameAddress']))
964 $aOrder[] = '(select count(*) from (select unnest(ARRAY['.join($aSearch['aFullNameAddress'],",").']) INTERSECT select unnest(nameaddress_vector))s) DESC';
969 $sSQL = "select place_id";
970 $sSQL .= " from search_name";
971 $sSQL .= " where ".join(' and ',$aTerms);
972 $sSQL .= " order by ".join(', ',$aOrder);
973 if ($aSearch['sHouseNumber'] || $aSearch['sClass'])
974 $sSQL .= " limit 50";
975 elseif (!sizeof($aSearch['aName']) && !sizeof($aSearch['aAddress']) && $aSearch['sClass'])
978 $sSQL .= " limit ".$iLimit;
980 if (CONST_Debug) { var_dump($sSQL); }
981 $aViewBoxPlaceIDs = $oDB->getAll($sSQL);
982 if (PEAR::IsError($aViewBoxPlaceIDs))
984 failInternalError("Could not get places for search terms.", $sSQL, $aViewBoxPlaceIDs);
986 //var_dump($aViewBoxPlaceIDs);
987 // Did we have an viewbox matches?
988 $aPlaceIDs = array();
989 $bViewBoxMatch = false;
990 foreach($aViewBoxPlaceIDs as $aViewBoxRow)
992 //if ($bViewBoxMatch == 1 && $aViewBoxRow['in_small'] == 'f') break;
993 //if ($bViewBoxMatch == 2 && $aViewBoxRow['in_large'] == 'f') break;
994 //if ($aViewBoxRow['in_small'] == 't') $bViewBoxMatch = 1;
995 //else if ($aViewBoxRow['in_large'] == 't') $bViewBoxMatch = 2;
996 $aPlaceIDs[] = $aViewBoxRow['place_id'];
999 //var_Dump($aPlaceIDs);
1002 if ($aSearch['sHouseNumber'] && sizeof($aPlaceIDs))
1004 $aRoadPlaceIDs = $aPlaceIDs;
1005 $sPlaceIDs = join(',',$aPlaceIDs);
1007 // Now they are indexed look for a house attached to a street we found
1008 $sHouseNumberRegex = '\\\\m'.str_replace(' ','[-,/ ]',$aSearch['sHouseNumber']).'\\\\M';
1009 $sSQL = "select place_id from placex where parent_place_id in (".$sPlaceIDs.") and housenumber ~* E'".$sHouseNumberRegex."'";
1010 if (sizeof($aExcludePlaceIDs))
1012 $sSQL .= " and place_id not in (".join(',',$aExcludePlaceIDs).")";
1014 $sSQL .= " limit $iLimit";
1015 if (CONST_Debug) var_dump($sSQL);
1016 $aPlaceIDs = $oDB->getCol($sSQL);
1018 // If not try the aux fallback table
1020 if (!sizeof($aPlaceIDs))
1022 $sSQL = "select place_id from location_property_aux where parent_place_id in (".$sPlaceIDs.") and housenumber = '".pg_escape_string($aSearch['sHouseNumber'])."'";
1023 if (sizeof($aExcludePlaceIDs))
1025 $sSQL .= " and place_id not in (".join(',',$aExcludePlaceIDs).")";
1027 //$sSQL .= " limit $iLimit";
1028 if (CONST_Debug) var_dump($sSQL);
1029 $aPlaceIDs = $oDB->getCol($sSQL);
1033 if (!sizeof($aPlaceIDs))
1035 $sSQL = "select place_id from location_property_tiger where parent_place_id in (".$sPlaceIDs.") and housenumber = '".pg_escape_string($aSearch['sHouseNumber'])."'";
1036 if (sizeof($aExcludePlaceIDs))
1038 $sSQL .= " and place_id not in (".join(',',$aExcludePlaceIDs).")";
1040 //$sSQL .= " limit $iLimit";
1041 if (CONST_Debug) var_dump($sSQL);
1042 $aPlaceIDs = $oDB->getCol($sSQL);
1045 // Fallback to the road
1046 if (!sizeof($aPlaceIDs) && preg_match('/[0-9]+/', $aSearch['sHouseNumber']))
1048 $aPlaceIDs = $aRoadPlaceIDs;
1053 if ($aSearch['sClass'] && sizeof($aPlaceIDs))
1055 $sPlaceIDs = join(',',$aPlaceIDs);
1056 $aClassPlaceIDs = array();
1058 if (!$aSearch['sOperator'] || $aSearch['sOperator'] == 'name')
1060 // If they were searching for a named class (i.e. 'Kings Head pub') then we might have an extra match
1061 $sSQL = "select place_id from placex where place_id in ($sPlaceIDs) and class='".$aSearch['sClass']."' and type='".$aSearch['sType']."'";
1062 $sSQL .= " and linked_place_id is null";
1063 if ($sCountryCodesSQL) $sSQL .= " and calculated_country_code in ($sCountryCodesSQL)";
1064 $sSQL .= " order by rank_search asc limit $iLimit";
1065 if (CONST_Debug) var_dump($sSQL);
1066 $aClassPlaceIDs = $oDB->getCol($sSQL);
1069 if (!$aSearch['sOperator'] || $aSearch['sOperator'] == 'near') // & in
1071 $sSQL = "select count(*) from pg_tables where tablename = 'place_classtype_".$aSearch['sClass']."_".$aSearch['sType']."'";
1072 $bCacheTable = $oDB->getOne($sSQL);
1074 $sSQL = "select min(rank_search) from placex where place_id in ($sPlaceIDs)";
1076 if (CONST_Debug) var_dump($sSQL);
1077 $iMaxRank = ((int)$oDB->getOne($sSQL));
1079 // For state / country level searches the normal radius search doesn't work very well
1080 $sPlaceGeom = false;
1081 if ($iMaxRank < 9 && $bCacheTable)
1083 // Try and get a polygon to search in instead
1084 $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";
1085 if (CONST_Debug) var_dump($sSQL);
1086 $sPlaceGeom = $oDB->getOne($sSQL);
1096 $sSQL = "select place_id from placex where place_id in ($sPlaceIDs) and rank_search < $iMaxRank";
1097 if (CONST_Debug) var_dump($sSQL);
1098 $aPlaceIDs = $oDB->getCol($sSQL);
1099 $sPlaceIDs = join(',',$aPlaceIDs);
1102 if ($sPlaceIDs || $sPlaceGeom)
1108 // More efficient - can make the range bigger
1112 if ($sNearPointSQL) $sOrderBySQL = "ST_Distance($sNearPointSQL, l.centroid)";
1113 else if ($sPlaceIDs) $sOrderBySQL = "ST_Distance(l.centroid, f.geometry)";
1114 else if ($sPlaceGeom) $sOrderBysSQL = "ST_Distance(st_centroid('".$sPlaceGeom."'), l.centroid)";
1116 $sSQL = "select distinct l.place_id".($sOrderBySQL?','.$sOrderBySQL:'')." from place_classtype_".$aSearch['sClass']."_".$aSearch['sType']." as l";
1117 if ($sCountryCodesSQL) $sSQL .= " join placex as lp using (place_id)";
1120 $sSQL .= ",placex as f where ";
1121 $sSQL .= "f.place_id in ($sPlaceIDs) and ST_DWithin(l.centroid, f.centroid, $fRange) ";
1126 $sSQL .= "ST_Contains('".$sPlaceGeom."', l.centroid) ";
1128 if (sizeof($aExcludePlaceIDs))
1130 $sSQL .= " and l.place_id not in (".join(',',$aExcludePlaceIDs).")";
1132 if ($sCountryCodesSQL) $sSQL .= " and lp.calculated_country_code in ($sCountryCodesSQL)";
1133 if ($sOrderBySQL) $sSQL .= "order by ".$sOrderBySQL." asc";
1134 if ($iOffset) $sSQL .= " offset $iOffset";
1135 $sSQL .= " limit $iLimit";
1136 if (CONST_Debug) var_dump($sSQL);
1137 $aClassPlaceIDs = array_merge($aClassPlaceIDs, $oDB->getCol($sSQL));
1141 if (isset($aSearch['fRadius']) && $aSearch['fRadius']) $fRange = $aSearch['fRadius'];
1144 if ($sNearPointSQL) $sOrderBySQL = "ST_Distance($sNearPointSQL, l.geometry)";
1145 else $sOrderBySQL = "ST_Distance(l.geometry, f.geometry)";
1147 $sSQL = "select distinct l.place_id".($sOrderBysSQL?','.$sOrderBysSQL:'')." from placex as l,placex as f where ";
1148 $sSQL .= "f.place_id in ( $sPlaceIDs) and ST_DWithin(l.geometry, f.centroid, $fRange) ";
1149 $sSQL .= "and l.class='".$aSearch['sClass']."' and l.type='".$aSearch['sType']."' ";
1150 if (sizeof($aExcludePlaceIDs))
1152 $sSQL .= " and l.place_id not in (".join(',',$aExcludePlaceIDs).")";
1154 if ($sCountryCodesSQL) $sSQL .= " and l.calculated_country_code in ($sCountryCodesSQL)";
1155 if ($sOrderBy) $sSQL .= "order by ".$OrderBysSQL." asc";
1156 if ($iOffset) $sSQL .= " offset $iOffset";
1157 $sSQL .= " limit $iLimit";
1158 if (CONST_Debug) var_dump($sSQL);
1159 $aClassPlaceIDs = array_merge($aClassPlaceIDs, $oDB->getCol($sSQL));
1164 $aPlaceIDs = $aClassPlaceIDs;
1170 if (PEAR::IsError($aPlaceIDs))
1172 failInternalError("Could not get place IDs from tokens." ,$sSQL, $aPlaceIDs);
1175 if (CONST_Debug) { echo "<br><b>Place IDs:</b> "; var_Dump($aPlaceIDs); }
1177 foreach($aPlaceIDs as $iPlaceID)
1179 $aResultPlaceIDs[$iPlaceID] = $iPlaceID;
1181 if ($iQueryLoop > 20) break;
1184 if (isset($aResultPlaceIDs) && sizeof($aResultPlaceIDs) && ($iMinAddressRank != 0 || $iMaxAddressRank != 30))
1186 // Need to verify passes rank limits before dropping out of the loop (yuk!)
1187 $sSQL = "select place_id from placex where place_id in (".join(',',$aResultPlaceIDs).") ";
1188 $sSQL .= "and (placex.rank_address between $iMinAddressRank and $iMaxAddressRank ";
1189 if (14 >= $iMinAddressRank && 14 <= $iMaxAddressRank) $sSQL .= " OR (extratags->'place') = 'city'";
1190 if ($aAddressRankList) $sSQL .= " OR placex.rank_address in (".join(',',$aAddressRankList).")";
1191 $sSQL .= ") UNION select place_id from location_property_tiger where place_id in (".join(',',$aResultPlaceIDs).") ";
1192 $sSQL .= "and (30 between $iMinAddressRank and $iMaxAddressRank ";
1193 if ($aAddressRankList) $sSQL .= " OR 30 in (".join(',',$aAddressRankList).")";
1195 if (CONST_Debug) var_dump($sSQL);
1196 $aResultPlaceIDs = $oDB->getCol($sSQL);
1201 if (isset($aResultPlaceIDs) && sizeof($aResultPlaceIDs)) break;
1202 if ($iGroupLoop > 4) break;
1203 if ($iQueryLoop > 30) break;
1206 // Did we find anything?
1207 if (isset($aResultPlaceIDs) && sizeof($aResultPlaceIDs))
1209 //var_Dump($aResultPlaceIDs);exit;
1210 // Get the details for display (is this a redundant extra step?)
1211 $sPlaceIDs = join(',',$aResultPlaceIDs);
1212 $sImportanceSQL = '';
1213 if ($sViewboxSmallSQL) $sImportanceSQL .= " case when ST_Contains($sViewboxSmallSQL, ST_Collect(centroid)) THEN 1 ELSE 0.75 END * ";
1214 if ($sViewboxLargeSQL) $sImportanceSQL .= " case when ST_Contains($sViewboxLargeSQL, ST_Collect(centroid)) THEN 1 ELSE 0.75 END * ";
1216 $sOrderSQL = 'CASE ';
1217 foreach(array_keys($aResultPlaceIDs) as $iOrder => $iPlaceID)
1219 $sOrderSQL .= 'when min(place_id) = '.$iPlaceID.' then '.$iOrder.' ';
1221 $sOrderSQL .= ' ELSE 10000000 END';
1222 $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,";
1223 $sSQL .= "get_address_by_language(place_id, $sLanguagePrefArraySQL) as langaddress,";
1224 $sSQL .= "get_name_by_language(name, $sLanguagePrefArraySQL) as placename,";
1225 $sSQL .= "get_name_by_language(name, ARRAY['ref']) as ref,";
1226 $sSQL .= "avg(ST_X(centroid)) as lon,avg(ST_Y(centroid)) as lat, ";
1227 //$sSQL .= $sOrderSQL." as porder, ";
1228 $sSQL .= $sImportanceSQL."coalesce(importance,0.75-(rank_search::float/40)) as importance, ";
1229 $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, ";
1230 $sSQL .= "(extratags->'place') as extra_place ";
1231 $sSQL .= "from placex where place_id in ($sPlaceIDs) ";
1232 $sSQL .= "and (placex.rank_address between $iMinAddressRank and $iMaxAddressRank ";
1233 if (14 >= $iMinAddressRank && 14 <= $iMaxAddressRank) $sSQL .= " OR (extratags->'place') = 'city'";
1234 if ($aAddressRankList) $sSQL .= " OR placex.rank_address in (".join(',',$aAddressRankList).")";
1236 if ($sAllowedTypesSQLList) $sSQL .= "and placex.class in $sAllowedTypesSQLList ";
1237 $sSQL .= "and linked_place_id is null ";
1238 $sSQL .= "group by osm_type,osm_id,class,type,admin_level,rank_search,rank_address,calculated_country_code,importance";
1239 if (!$bDeDupe) $sSQL .= ",place_id";
1240 $sSQL .= ",langaddress ";
1241 $sSQL .= ",placename ";
1243 $sSQL .= ",extratags->'place' ";
1245 $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,";
1246 $sSQL .= "get_address_by_language(place_id, $sLanguagePrefArraySQL) as langaddress,";
1247 $sSQL .= "null as placename,";
1248 $sSQL .= "null as ref,";
1249 $sSQL .= "avg(ST_X(centroid)) as lon,avg(ST_Y(centroid)) as lat, ";
1250 //$sSQL .= $sOrderSQL." as porder, ";
1251 $sSQL .= $sImportanceSQL."0.015 as importance, ";
1252 $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, ";
1253 $sSQL .= "null as extra_place ";
1254 $sSQL .= "from location_property_tiger where place_id in ($sPlaceIDs) ";
1255 $sSQL .= "and 30 between $iMinAddressRank and $iMaxAddressRank ";
1256 $sSQL .= "group by place_id";
1257 if (!$bDeDupe) $sSQL .= ",place_id";
1260 $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,";
1261 $sSQL .= "get_address_by_language(place_id, $sLanguagePrefArraySQL) as langaddress,";
1262 $sSQL .= "null as placename,";
1263 $sSQL .= "null as ref,";
1264 $sSQL .= "avg(ST_X(centroid)) as lon,avg(ST_Y(centroid)) as lat, ";
1265 //$sSQL .= $sOrderSQL." as porder, ";
1266 $sSQL .= $sImportanceSQL."0.01 as importance, ";
1267 $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, ";
1268 $sSQL .= "null as extra_place ";
1269 $sSQL .= "from location_property_aux where place_id in ($sPlaceIDs) ";
1270 $sSQL .= "and 30 between $iMinAddressRank and $iMaxAddressRank ";
1271 $sSQL .= "group by place_id";
1272 if (!$bDeDupe) $sSQL .= ",place_id";
1273 $sSQL .= ",get_address_by_language(place_id, $sLanguagePrefArraySQL) ";
1274 $sSQL .= "order by importance desc";
1275 //$sSQL .= "order by rank_search,rank_address,porder asc";
1277 if (CONST_Debug) { echo "<hr>"; var_dump($sSQL); }
1278 $aSearchResults = $oDB->getAll($sSQL);
1279 //var_dump($sSQL,$aSearchResults);exit;
1281 if (PEAR::IsError($aSearchResults))
1283 failInternalError("Could not get details for place.", $sSQL, $aSearchResults);
1286 } // end if ($sQuery)
1289 if (isset($_GET['nearlat']) && trim($_GET['nearlat'])!=='' && isset($_GET['nearlon']) && trim($_GET['nearlon']) !== '')
1291 $iPlaceID = geocodeReverse((float)$_GET['nearlat'], (float)$_GET['nearlon']);
1295 $aResultPlaceIDs = array($iPlaceID);
1296 // TODO: this needs refactoring!
1298 // Get the details for display (is this a redundant extra step?)
1299 $sPlaceIDs = join(',',$aResultPlaceIDs);
1300 $sOrderSQL = 'CASE ';
1301 foreach(array_keys($aResultPlaceIDs) as $iOrder => $iPlaceID)
1303 $sOrderSQL .= 'when min(place_id) = '.$iPlaceID.' then '.$iOrder.' ';
1305 $sOrderSQL .= ' ELSE 10000000 END';
1306 $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,";
1307 $sSQL .= "get_address_by_language(place_id, $sLanguagePrefArraySQL) as langaddress,";
1308 $sSQL .= "get_name_by_language(name, $sLanguagePrefArraySQL) as placename,";
1309 $sSQL .= "get_name_by_language(name, ARRAY['ref']) as ref,";
1310 $sSQL .= "avg(ST_X(centroid)) as lon,avg(ST_Y(centroid)) as lat, ";
1311 //$sSQL .= $sOrderSQL." as porder, ";
1312 $sSQL .= "coalesce(importance,0.75-(rank_search::float/40)) as importance, ";
1313 $sSQL .= "(extratags->'place') as extra_place ";
1314 $sSQL .= "from placex where place_id in ($sPlaceIDs) ";
1315 $sSQL .= "and (placex.rank_address between $iMinAddressRank and $iMaxAddressRank ";
1316 if (14 >= $iMinAddressRank && 14 <= $iMaxAddressRank) $sSQL .= " OR (extratags->'place') = 'city'";
1317 if ($aAddressRankList) $sSQL .= " OR placex.rank_address in (".join(',',$aAddressRankList).")";
1319 $sSQL .= "group by osm_type,osm_id,class,type,admin_level,rank_search,rank_address,calculated_country_code,importance";
1320 if (!$bDeDupe) $sSQL .= ",place_id";
1321 $sSQL .= ",get_address_by_language(place_id, $sLanguagePrefArraySQL) ";
1322 $sSQL .= ",get_name_by_language(name, $sLanguagePrefArraySQL) ";
1323 $sSQL .= ",get_name_by_language(name, ARRAY['ref']) ";
1324 $sSQL .= ",extratags->'place' ";
1326 $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,";
1327 $sSQL .= "get_address_by_language(place_id, $sLanguagePrefArraySQL) as langaddress,";
1328 $sSQL .= "null as placename,";
1329 $sSQL .= "null as ref,";
1330 $sSQL .= "avg(ST_X(centroid)) as lon,avg(ST_Y(centroid)) as lat, ";
1331 //$sSQL .= $sOrderSQL." as porder, ";
1332 $sSQL .= "-0.15 as importance, ";
1333 $sSQL .= "null as extra_place ";
1334 $sSQL .= "from location_property_tiger where place_id in ($sPlaceIDs) ";
1335 $sSQL .= "and 30 between $iMinAddressRank and $iMaxAddressRank ";
1336 $sSQL .= "group by place_id";
1337 if (!$bDeDupe) $sSQL .= ",place_id";
1340 $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,";
1341 $sSQL .= "get_address_by_language(place_id, $sLanguagePrefArraySQL) as langaddress,";
1342 $sSQL .= "null as placename,";
1343 $sSQL .= "null as ref,";
1344 $sSQL .= "avg(ST_X(centroid)) as lon,avg(ST_Y(centroid)) as lat, ";
1345 //$sSQL .= $sOrderSQL." as porder, ";
1346 $sSQL .= "-0.10 as importance, ";
1347 $sSQL .= "null as extra_place ";
1348 $sSQL .= "from location_property_aux where place_id in ($sPlaceIDs) ";
1349 $sSQL .= "and 30 between $iMinAddressRank and $iMaxAddressRank ";
1350 $sSQL .= "group by place_id";
1352 if (!$bDeDupe) $sSQL .= ",place_id";
1353 $sSQL .= ",get_address_by_language(place_id, $sLanguagePrefArraySQL) ";
1354 $sSQL .= "order by importance desc";
1355 //$sSQL .= "order by rank_search,rank_address,porder asc";
1356 if (CONST_Debug) { echo "<hr>", var_dump($sSQL); }
1357 $aSearchResults = $oDB->getAll($sSQL);
1358 //var_dump($sSQL,$aSearchResults);exit;
1360 if (PEAR::IsError($aSearchResults))
1362 failInternalError("Could not get details for place (near).", $sSQL, $aSearchResults);
1367 $aSearchResults = array();
1373 $sSearchResult = '';
1374 if (!sizeof($aSearchResults) && isset($_GET['q']) && $_GET['q'])
1376 $sSearchResult = 'No Results Found';
1378 //var_Dump($aSearchResults);
1380 $aClassType = getClassTypesWithImportance();
1381 $aRecheckWords = preg_split('/\b/u',$sQuery);
1382 foreach($aRecheckWords as $i => $sWord)
1384 if (!$sWord) unset($aRecheckWords[$i]);
1386 foreach($aSearchResults as $iResNum => $aResult)
1388 if (CONST_Search_AreaPolygons)
1390 // Get the bounding box and outline polygon
1391 $sSQL = "select place_id,numfeatures,area,outline,";
1392 $sSQL .= "ST_Y(ST_PointN(ST_ExteriorRing(Box2D(outline)),4)) as minlat,ST_Y(ST_PointN(ST_ExteriorRing(Box2D(outline)),2)) as maxlat,";
1393 $sSQL .= "ST_X(ST_PointN(ST_ExteriorRing(Box2D(outline)),1)) as minlon,ST_X(ST_PointN(ST_ExteriorRing(Box2D(outline)),3)) as maxlon,";
1394 $sSQL .= "ST_AsText(outline) as outlinestring from get_place_boundingbox_quick(".$aResult['place_id'].")";
1396 $sSQL = "select place_id,0 as numfeatures,st_area(geometry) as area,";
1397 $sSQL .= "ST_Y(centroid) as centrelat,ST_X(centroid) as centrelon,";
1398 $sSQL .= "ST_Y(ST_PointN(ST_ExteriorRing(Box2D(geometry)),4)) as minlat,ST_Y(ST_PointN(ST_ExteriorRing(Box2D(geometry)),2)) as maxlat,";
1399 $sSQL .= "ST_X(ST_PointN(ST_ExteriorRing(Box2D(geometry)),1)) as minlon,ST_X(ST_PointN(ST_ExteriorRing(Box2D(geometry)),3)) as maxlon";
1400 if ($bAsGeoJSON) $sSQL .= ",ST_AsGeoJSON(geometry) as asgeojson";
1401 if ($bAsKML) $sSQL .= ",ST_AsKML(geometry) as askml";
1402 if ($bAsSVG) $sSQL .= ",ST_AsSVG(geometry) as assvg";
1403 if ($bAsText || $bShowPolygons) $sSQL .= ",ST_AsText(geometry) as astext";
1404 $sSQL .= " from placex where place_id = ".$aResult['place_id'].' and st_geometrytype(Box2D(geometry)) = \'ST_Polygon\'';
1405 $aPointPolygon = $oDB->getRow($sSQL);
1406 if (PEAR::IsError($aPointPolygon))
1408 failInternalError("Could not get outline.", $sSQL, $aPointPolygon);
1410 if ($aPointPolygon['place_id'])
1412 if ($bAsGeoJSON) $aResult['asgeojson'] = $aPointPolygon['asgeojson'];
1413 if ($bAsKML) $aResult['askml'] = $aPointPolygon['askml'];
1414 if ($bAsSVG) $aResult['assvg'] = $aPointPolygon['assvg'];
1415 if ($bAsText) $aResult['astext'] = $aPointPolygon['astext'];
1417 if ($aPointPolygon['centrelon'] !== null && $aPointPolygon['centrelat'] !== null )
1419 $aResult['lat'] = $aPointPolygon['centrelat'];
1420 $aResult['lon'] = $aPointPolygon['centrelon'];
1424 // Translate geometary string to point array
1425 if (preg_match('#POLYGON\\(\\(([- 0-9.,]+)#',$aPointPolygon['astext'],$aMatch))
1427 preg_match_all('/(-?[0-9.]+) (-?[0-9.]+)/',$aMatch[1],$aPolyPoints,PREG_SET_ORDER);
1429 /*elseif (preg_match('#MULTIPOLYGON\\(\\(\\(([- 0-9.,]+)#',$aPointPolygon['astext'],$aMatch))
1431 preg_match_all('/(-?[0-9.]+) (-?[0-9.]+)/',$aMatch[1],$aPolyPoints,PREG_SET_ORDER);
1433 elseif (preg_match('#POINT\\((-?[0-9.]+) (-?[0-9.]+)\\)#',$aPointPolygon['astext'],$aMatch))
1436 $iSteps = ($fRadius * 40000)^2;
1437 $fStepSize = (2*pi())/$iSteps;
1438 $aPolyPoints = array();
1439 for($f = 0; $f < 2*pi(); $f += $fStepSize)
1441 $aPolyPoints[] = array('',$aMatch[1]+($fRadius*sin($f)),$aMatch[2]+($fRadius*cos($f)));
1443 $aPointPolygon['minlat'] = $aPointPolygon['minlat'] - $fRadius;
1444 $aPointPolygon['maxlat'] = $aPointPolygon['maxlat'] + $fRadius;
1445 $aPointPolygon['minlon'] = $aPointPolygon['minlon'] - $fRadius;
1446 $aPointPolygon['maxlon'] = $aPointPolygon['maxlon'] + $fRadius;
1450 // Output data suitable for display (points and a bounding box)
1451 if ($bShowPolygons && isset($aPolyPoints))
1453 $aResult['aPolyPoints'] = array();
1454 foreach($aPolyPoints as $aPoint)
1456 $aResult['aPolyPoints'][] = array($aPoint[1], $aPoint[2]);
1459 $aResult['aBoundingBox'] = array($aPointPolygon['minlat'],$aPointPolygon['maxlat'],$aPointPolygon['minlon'],$aPointPolygon['maxlon']);
1463 if ($aResult['extra_place'] == 'city')
1465 $aResult['class'] = 'place';
1466 $aResult['type'] = 'city';
1467 $aResult['rank_search'] = 16;
1470 if (!isset($aResult['aBoundingBox']))
1473 $fDiameter = 0.0001;
1475 if (isset($aClassType[$aResult['class'].':'.$aResult['type'].':'.$aResult['admin_level']]['defdiameter'])
1476 && $aClassType[$aResult['class'].':'.$aResult['type'].':'.$aResult['admin_level']]['defdiameter'])
1478 $fDiameter = $aClassType[$aResult['class'].':'.$aResult['type'].':'.$aResult['admin_level']]['defzoom'];
1480 elseif (isset($aClassType[$aResult['class'].':'.$aResult['type']]['defdiameter'])
1481 && $aClassType[$aResult['class'].':'.$aResult['type']]['defdiameter'])
1483 $fDiameter = $aClassType[$aResult['class'].':'.$aResult['type']]['defdiameter'];
1485 $fRadius = $fDiameter / 2;
1487 $iSteps = max(8,min(100,$fRadius * 3.14 * 100000));
1488 $fStepSize = (2*pi())/$iSteps;
1489 $aPolyPoints = array();
1490 for($f = 0; $f < 2*pi(); $f += $fStepSize)
1492 $aPolyPoints[] = array('',$aResult['lon']+($fRadius*sin($f)),$aResult['lat']+($fRadius*cos($f)));
1494 $aPointPolygon['minlat'] = $aResult['lat'] - $fRadius;
1495 $aPointPolygon['maxlat'] = $aResult['lat'] + $fRadius;
1496 $aPointPolygon['minlon'] = $aResult['lon'] - $fRadius;
1497 $aPointPolygon['maxlon'] = $aResult['lon'] + $fRadius;
1499 // Output data suitable for display (points and a bounding box)
1502 $aResult['aPolyPoints'] = array();
1503 foreach($aPolyPoints as $aPoint)
1505 $aResult['aPolyPoints'][] = array($aPoint[1], $aPoint[2]);
1508 $aResult['aBoundingBox'] = array($aPointPolygon['minlat'],$aPointPolygon['maxlat'],$aPointPolygon['minlon'],$aPointPolygon['maxlon']);
1511 // Is there an icon set for this type of result?
1512 if (isset($aClassType[$aResult['class'].':'.$aResult['type']]['icon'])
1513 && $aClassType[$aResult['class'].':'.$aResult['type']]['icon'])
1515 $aResult['icon'] = CONST_Website_BaseURL.'images/mapicons/'.$aClassType[$aResult['class'].':'.$aResult['type']]['icon'].'.p.20.png';
1518 if (isset($aClassType[$aResult['class'].':'.$aResult['type']]['label'])
1519 && $aClassType[$aResult['class'].':'.$aResult['type']]['label'])
1521 $aResult['label'] = $aClassType[$aResult['class'].':'.$aResult['type']]['label'];
1524 if ($bShowAddressDetails)
1526 $aResult['address'] = getAddressDetails($oDB, $sLanguagePrefArraySQL, $aResult['place_id'], $aResult['country_code']);
1527 if ($aResult['extra_place'] == 'city' && !isset($aResult['address']['city']))
1529 $aResult['address'] = array_merge(array('city' => array_shift(array_values($aResult['address']))), $aResult['address']);
1532 //var_dump($aResult['address']);
1536 // Adjust importance for the number of exact string matches in the result
1537 $aResult['importance'] = max(0.001,$aResult['importance']);
1539 $sAddress = $aResult['langaddress'];
1540 foreach($aRecheckWords as $i => $sWord)
1542 if (stripos($sAddress, $sWord)!==false) $iCountWords++;
1545 $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
1547 //if (CONST_Debug) var_dump($aResult['class'].':'.$aResult['type'].':'.$aResult['admin_level']);
1549 if (isset($aClassType[$aResult['class'].':'.$aResult['type'].':'.$aResult['admin_level']]['importance'])
1550 && $aClassType[$aResult['class'].':'.$aResult['type'].':'.$aResult['admin_level']]['importance'])
1552 $aResult['importance'] = $aClassType[$aResult['class'].':'.$aResult['type'].':'.$aResult['admin_level']]['importance'];
1554 elseif (isset($aClassType[$aResult['class'].':'.$aResult['type']]['importance'])
1555 && $aClassType[$aResult['class'].':'.$aResult['type']]['importance'])
1557 $aResult['importance'] = $aClassType[$aResult['class'].':'.$aResult['type']]['importance'];
1561 $aResult['importance'] = 1000000000000000;
1564 $aResult['name'] = $aResult['langaddress'];
1565 $aResult['foundorder'] = -$aResult['addressimportance'];
1566 $aSearchResults[$iResNum] = $aResult;
1568 uasort($aSearchResults, 'byImportance');
1570 $aOSMIDDone = array();
1571 $aClassTypeNameDone = array();
1572 $aToFilter = $aSearchResults;
1573 $aSearchResults = array();
1576 foreach($aToFilter as $iResNum => $aResult)
1578 if ($aResult['type'] == 'adminitrative') $aResult['type'] = 'administrative';
1579 $aExcludePlaceIDs[$aResult['place_id']] = $aResult['place_id'];
1582 $fLat = $aResult['lat'];
1583 $fLon = $aResult['lon'];
1584 if (isset($aResult['zoom'])) $iZoom = $aResult['zoom'];
1587 if (!$bDeDupe || (!isset($aOSMIDDone[$aResult['osm_type'].$aResult['osm_id']])
1588 && !isset($aClassTypeNameDone[$aResult['osm_type'].$aResult['class'].$aResult['type'].$aResult['name'].$aResult['admin_level']])))
1590 $aOSMIDDone[$aResult['osm_type'].$aResult['osm_id']] = true;
1591 $aClassTypeNameDone[$aResult['osm_type'].$aResult['class'].$aResult['type'].$aResult['name'].$aResult['admin_level']] = true;
1592 $aSearchResults[] = $aResult;
1595 // Absolute limit on number of results
1596 if (sizeof($aSearchResults) >= $iFinalLimit) break;
1599 $sDataDate = $oDB->getOne("select TO_CHAR(lastimportdate - '2 minutes'::interval,'YYYY/MM/DD HH24:MI')||' GMT' from import_status limit 1");
1601 if (isset($_GET['nearlat']) && isset($_GET['nearlon']))
1603 $sQuery .= ' ['.$_GET['nearlat'].','.$_GET['nearlon'].']';
1608 logEnd($oDB, $hLog, sizeof($aToFilter));
1610 $sMoreURL = CONST_Website_BaseURL.'search?format='.urlencode($sOutputFormat).'&exclude_place_ids='.join(',',$aExcludePlaceIDs);
1611 if (isset($_SERVER["HTTP_ACCEPT_LANGUAGE"])) $sMoreURL .= '&accept-language='.$_SERVER["HTTP_ACCEPT_LANGUAGE"];
1612 if ($bShowPolygons) $sMoreURL .= '&polygon=1';
1613 if ($bShowAddressDetails) $sMoreURL .= '&addressdetails=1';
1614 if (isset($_GET['viewbox']) && $_GET['viewbox']) $sMoreURL .= '&viewbox='.urlencode($_GET['viewbox']);
1615 if (isset($_GET['nearlat']) && isset($_GET['nearlon'])) $sMoreURL .= '&nearlat='.(float)$_GET['nearlat'].'&nearlon='.(float)$_GET['nearlon'];
1616 $sMoreURL .= '&q='.urlencode($sQuery);
1618 if (CONST_Debug) exit;
1620 include(CONST_BasePath.'/lib/template/search-'.$sOutputFormat.'.php');