2 @define('CONST_ConnectionBucket_PageType', 'Search');
4 require_once(dirname(dirname(__FILE__)).'/lib/init-website.php');
5 require_once(CONST_BasePath.'/lib/log.php');
7 ini_set('memory_limit', '200M');
12 $fLat = CONST_Default_Lat;
13 $fLon = CONST_Default_Lon;
14 $iZoom = CONST_Default_Zoom;
15 $bBoundingBoxSearch = isset($_GET['bounded'])?(bool)$_GET['bounded']:false;
16 $sOutputFormat = 'html';
17 $aSearchResults = array();
18 $aExcludePlaceIDs = array();
19 $sCountryCodesSQL = false;
20 $bDeDupe = isset($_GET['dedupe'])?(bool)$_GET['dedupe']:true;
21 $bReverseInPlan = false;
22 $iFinalLimit = isset($_GET['limit'])?(int)$_GET['limit']:10;
23 $iOffset = isset($_GET['offset'])?(int)$_GET['offset']:0;
25 if ($iFinalLimit > 50) $iFinalLimit = 50;
26 $iLimit = $iFinalLimit + min($iFinalLimit, 10);
28 $iMaxAddressRank = 30;
29 $aAddressRankList = array();
30 $sAllowedTypesSQLList = false;
33 if (isset($_GET['format']) && ($_GET['format'] == 'html' || $_GET['format'] == 'xml' || $_GET['format'] == 'json' || $_GET['format'] == 'jsonv2'))
35 $sOutputFormat = $_GET['format'];
38 // Show / use polygons
39 $bShowPolygons = (boolean)isset($_GET['polygon']) && $_GET['polygon'];
40 if ($sOutputFormat == 'html')
42 $bAsText = $bShowPolygons;
43 $bShowPolygons = false;
50 $bAsGeoJSON = (boolean)isset($_GET['polygon_geojson']) && $_GET['polygon_geojson'];
51 $bAsKML = (boolean)isset($_GET['polygon_kml']) && $_GET['polygon_kml'];
52 $bAsSVG = (boolean)isset($_GET['polygon_svg']) && $_GET['polygon_svg'];
53 $bAsText = (boolean)isset($_GET['polygon_text']) && $_GET['polygon_text'];
54 if ((($bShowPolygons?1:0)
59 ) > CONST_PolygonOutput_MaximumTypes)
61 if (CONST_PolygonOutput_MaximumTypes)
63 userError("Select only ".CONST_PolygonOutput_MaximumTypes." polgyon output option");
67 userError("Polygon output is disabled");
73 // Show address breakdown
74 $bShowAddressDetails = isset($_GET['addressdetails']) && $_GET['addressdetails'];
77 $aLangPrefOrder = getPreferredLanguages();
78 $bReverseInPlan = true;
80 if (isset($aLangPrefOrder['name:de'])) $bReverseInPlan = true;
81 if (isset($aLangPrefOrder['name:ru'])) $bReverseInPlan = true;
82 if (isset($aLangPrefOrder['name:ja'])) $bReverseInPlan = true;
83 if (isset($aLangPrefOrder['name:pl'])) $bReverseInPlan = true;
86 $sLanguagePrefArraySQL = "ARRAY[".join(',',array_map("getDBQuoted",$aLangPrefOrder))."]";
88 if (isset($_GET['exclude_place_ids']) && $_GET['exclude_place_ids'])
90 foreach(explode(',',$_GET['exclude_place_ids']) as $iExcludedPlaceID)
92 $iExcludedPlaceID = (int)$iExcludedPlaceID;
93 if ($iExcludedPlaceID) $aExcludePlaceIDs[$iExcludedPlaceID] = $iExcludedPlaceID;
97 // Only certain ranks of feature
98 if (isset($_GET['featureType']) && !isset($_GET['featuretype'])) $_GET['featuretype'] = $_GET['featureType'];
100 if (isset($_GET['featuretype']))
102 switch($_GET['featuretype'])
105 $iMinAddressRank = $iMaxAddressRank = 4;
108 $iMinAddressRank = $iMaxAddressRank = 8;
111 $iMinAddressRank = 14;
112 $iMaxAddressRank = 16;
115 $iMinAddressRank = 8;
116 $iMaxAddressRank = 20;
121 if (isset($_GET['countrycodes']))
123 $aCountryCodes = array();
124 foreach(explode(',',$_GET['countrycodes']) as $sCountryCode)
126 if (preg_match('/^[a-zA-Z][a-zA-Z]$/', $sCountryCode))
128 $aCountryCodes[] = "'".strtolower($sCountryCode)."'";
131 $sCountryCodesSQL = join(',', $aCountryCodes);
135 $sQuery = (isset($_GET['q'])?trim($_GET['q']):'');
136 if (!$sQuery && isset($_SERVER['PATH_INFO']) && $_SERVER['PATH_INFO'][0] == '/')
138 $sQuery = substr($_SERVER['PATH_INFO'], 1);
140 // reverse order of '/' separated string
141 $aPhrases = explode('/', $sQuery);
142 $aPhrases = array_reverse($aPhrases);
143 $sQuery = join(', ',$aPhrases);
147 $aStructuredOptions = array(
148 array('amenity', 26, 30, false),
149 array('street', 26, 30, false),
150 array('city', 14, 24, false),
151 array('county', 9, 13, false),
152 array('state', 8, 8, false),
153 array('country', 4, 4, false),
154 array('postalcode', 5, 11, array(5, 11)),
156 $aStructuredQuery = array();
157 $sAllowedTypesSQLList = '';
158 foreach($aStructuredOptions as $aStructuredOption)
160 loadStructuredAddressElement($aStructuredQuery, $iMinAddressRank, $iMaxAddressRank, $aAddressRankList, $_GET, $aStructuredOption[0], $aStructuredOption[1], $aStructuredOption[2], $aStructuredOption[3]);
162 if (sizeof($aStructuredQuery) > 0)
164 $sQuery = join(', ', $aStructuredQuery);
165 if ($iMaxAddressRank < 30)
167 $sAllowedTypesSQLList = '(\'place\',\'boundary\')';
173 $hLog = logStart($oDB, 'search', $sQuery, $aLangPrefOrder);
175 // Hack to make it handle "new york, ny" (and variants) correctly
176 $sQuery = str_ireplace(array('New York, ny','new york, new york', 'New York ny','new york new york'), 'new york city, ny', $sQuery);
177 if (isset($aLangPrefOrder['name:en']))
179 $sQuery = preg_replace('/,\s*il\s*(,|$)/',', illinois\1', $sQuery);
180 $sQuery = preg_replace('/,\s*al\s*(,|$)/',', alabama\1', $sQuery);
181 $sQuery = preg_replace('/,\s*la\s*(,|$)/',', louisiana\1', $sQuery);
184 // If we have a view box create the SQL
185 // Small is the actual view box, Large is double (on each axis) that
186 $sViewboxCentreSQL = $sViewboxSmallSQL = $sViewboxLargeSQL = false;
187 if (isset($_GET['viewboxlbrt']) && $_GET['viewboxlbrt'])
189 $aCoOrdinatesLBRT = explode(',',$_GET['viewboxlbrt']);
190 $_GET['viewbox'] = $aCoOrdinatesLBRT[0].','.$aCoOrdinatesLBRT[3].','.$aCoOrdinatesLBRT[2].','.$aCoOrdinatesLBRT[1];
192 if (isset($_GET['viewbox']) && $_GET['viewbox'])
194 $aCoOrdinates = explode(',',$_GET['viewbox']);
195 $sViewboxSmallSQL = "ST_SetSRID(ST_MakeBox2D(ST_Point(".(float)$aCoOrdinates[0].",".(float)$aCoOrdinates[1]."),ST_Point(".(float)$aCoOrdinates[2].",".(float)$aCoOrdinates[3].")),4326)";
196 $fHeight = $aCoOrdinates[0]-$aCoOrdinates[2];
197 $fWidth = $aCoOrdinates[1]-$aCoOrdinates[3];
198 $aCoOrdinates[0] += $fHeight;
199 $aCoOrdinates[2] -= $fHeight;
200 $aCoOrdinates[1] += $fWidth;
201 $aCoOrdinates[3] -= $fWidth;
202 $sViewboxLargeSQL = "ST_SetSRID(ST_MakeBox2D(ST_Point(".(float)$aCoOrdinates[0].",".(float)$aCoOrdinates[1]."),ST_Point(".(float)$aCoOrdinates[2].",".(float)$aCoOrdinates[3].")),4326)";
206 $bBoundingBoxSearch = false;
208 if (isset($_GET['route']) && $_GET['route'] && isset($_GET['routewidth']) && $_GET['routewidth'])
210 $aPoints = explode(',',$_GET['route']);
211 if (sizeof($aPoints) % 2 != 0)
213 userError("Uneven number of points");
216 $sViewboxCentreSQL = "ST_SetSRID('LINESTRING(";
218 foreach($aPoints as $i => $fPoint)
222 if ($i != 1) $sViewboxCentreSQL .= ",";
223 $sViewboxCentreSQL .= ((float)$fPoint).' '.$fPrevCoord;
227 $fPrevCoord = (float)$fPoint;
230 $sViewboxCentreSQL .= ")'::geometry,4326)";
232 $sSQL = "select st_buffer(".$sViewboxCentreSQL.",".(float)($_GET['routewidth']/69).")";
233 $sViewboxSmallSQL = $oDB->getOne($sSQL);
234 if (PEAR::isError($sViewboxSmallSQL))
236 failInternalError("Could not get small viewbox.", $sSQL, $sViewboxSmallSQL);
238 $sViewboxSmallSQL = "'".$sViewboxSmallSQL."'::geometry";
240 $sSQL = "select st_buffer(".$sViewboxCentreSQL.",".(float)($_GET['routewidth']/30).")";
241 $sViewboxLargeSQL = $oDB->getOne($sSQL);
242 if (PEAR::isError($sViewboxLargeSQL))
244 failInternalError("Could not get large viewbox.", $sSQL, $sViewboxLargeSQL);
246 $sViewboxLargeSQL = "'".$sViewboxLargeSQL."'::geometry";
247 $bBoundingBoxSearch = true;
250 // Do we have anything that looks like a lat/lon pair?
251 if (preg_match('/\\b([NS])[ ]+([0-9]+[0-9.]*)[ ]+([0-9.]+)?[, ]+([EW])[ ]+([0-9]+)[ ]+([0-9]+[0-9.]*)?\\b/', $sQuery, $aData))
253 $fQueryLat = ($aData[1]=='N'?1:-1) * ($aData[2] + $aData[3]/60);
254 $fQueryLon = ($aData[4]=='E'?1:-1) * ($aData[5] + $aData[6]/60);
255 if ($fQueryLat <= 90.1 && $fQueryLat >= -90.1 && $fQueryLon <= 180.1 && $fQueryLon >= -180.1)
257 $_GET['nearlat'] = $fQueryLat;
258 $_GET['nearlon'] = $fQueryLon;
259 $sQuery = trim(str_replace($aData[0], ' ', $sQuery));
262 elseif (preg_match('/\\b([0-9]+)[ ]+([0-9]+[0-9.]*)?[ ]+([NS])[, ]+([0-9]+)[ ]+([0-9]+[0-9.]*)?[ ]+([EW])\\b/', $sQuery, $aData))
264 $fQueryLat = ($aData[3]=='N'?1:-1) * ($aData[1] + $aData[2]/60);
265 $fQueryLon = ($aData[6]=='E'?1:-1) * ($aData[4] + $aData[5]/60);
266 if ($fQueryLat <= 90.1 && $fQueryLat >= -90.1 && $fQueryLon <= 180.1 && $fQueryLon >= -180.1)
268 $_GET['nearlat'] = $fQueryLat;
269 $_GET['nearlon'] = $fQueryLon;
270 $sQuery = trim(str_replace($aData[0], ' ', $sQuery));
273 elseif (preg_match('/(\\[|^|\\b)(-?[0-9]+[0-9]*\\.[0-9]+)[, ]+(-?[0-9]+[0-9]*\\.[0-9]+)(\\]|$|\\b)/', $sQuery, $aData))
275 $fQueryLat = $aData[2];
276 $fQueryLon = $aData[3];
277 if ($fQueryLat <= 90.1 && $fQueryLat >= -90.1 && $fQueryLon <= 180.1 && $fQueryLon >= -180.1)
279 $_GET['nearlat'] = $fQueryLat;
280 $_GET['nearlon'] = $fQueryLon;
281 $sQuery = trim(str_replace($aData[0], ' ', $sQuery));
285 if ($sQuery || $aStructuredQuery)
287 // Start with a blank search
289 array('iSearchRank' => 0, 'iNamePhrase' => -1, 'sCountryCode' => false, 'aName'=>array(), 'aAddress'=>array(), 'aFullNameAddress'=>array(),
290 'aNameNonSearch'=>array(), 'aAddressNonSearch'=>array(),
291 'sOperator'=>'', 'aFeatureName' => array(), 'sClass'=>'', 'sType'=>'', 'sHouseNumber'=>'', 'fLat'=>'', 'fLon'=>'', 'fRadius'=>'')
294 $sNearPointSQL = false;
295 if (isset($_GET['nearlat']) && isset($_GET['nearlon']))
297 $sNearPointSQL = "ST_SetSRID(ST_Point(".(float)$_GET['nearlon'].",".(float)$_GET['nearlat']."),4326)";
298 $aSearches[0]['fLat'] = (float)$_GET['nearlat'];
299 $aSearches[0]['fLon'] = (float)$_GET['nearlon'];
300 $aSearches[0]['fRadius'] = 0.1;
303 $bSpecialTerms = false;
304 preg_match_all('/\\[(.*)=(.*)\\]/', $sQuery, $aSpecialTermsRaw, PREG_SET_ORDER);
305 $aSpecialTerms = array();
306 foreach($aSpecialTermsRaw as $aSpecialTerm)
308 $sQuery = str_replace($aSpecialTerm[0], ' ', $sQuery);
309 $aSpecialTerms[strtolower($aSpecialTerm[1])] = $aSpecialTerm[2];
312 preg_match_all('/\\[([\\w ]*)\\]/u', $sQuery, $aSpecialTermsRaw, PREG_SET_ORDER);
313 $aSpecialTerms = array();
314 if (isset($aStructuredQuery['amenity']) && $aStructuredQuery['amenity'])
316 $aSpecialTermsRaw[] = array('['.$aStructuredQuery['amenity'].']', $aStructuredQuery['amenity']);
317 unset($aStructuredQuery['amenity']);
319 foreach($aSpecialTermsRaw as $aSpecialTerm)
321 $sQuery = str_replace($aSpecialTerm[0], ' ', $sQuery);
322 $sToken = $oDB->getOne("select make_standard_name('".$aSpecialTerm[1]."') as string");
323 $sSQL = 'select * from (select word_id,word_token, word, class, type, country_code, operator';
324 $sSQL .= ' from word where word_token in (\' '.$sToken.'\')) as x where (class is not null and class not in (\'place\')) or country_code is not null';
325 if (CONST_Debug) var_Dump($sSQL);
326 $aSearchWords = $oDB->getAll($sSQL);
327 $aNewSearches = array();
328 foreach($aSearches as $aSearch)
330 foreach($aSearchWords as $aSearchTerm)
332 $aNewSearch = $aSearch;
333 if ($aSearchTerm['country_code'])
335 $aNewSearch['sCountryCode'] = strtolower($aSearchTerm['country_code']);
336 $aNewSearches[] = $aNewSearch;
337 $bSpecialTerms = true;
339 if ($aSearchTerm['class'])
341 $aNewSearch['sClass'] = $aSearchTerm['class'];
342 $aNewSearch['sType'] = $aSearchTerm['type'];
343 $aNewSearches[] = $aNewSearch;
344 $bSpecialTerms = true;
348 $aSearches = $aNewSearches;
351 // Split query into phrases
352 // Commas are used to reduce the search space by indicating where phrases split
353 if (sizeof($aStructuredQuery) > 0)
355 $aPhrases = $aStructuredQuery;
356 $bStructuredPhrases = true;
360 $aPhrases = explode(',',$sQuery);
361 $bStructuredPhrases = false;
364 // Convert each phrase to standard form
365 // Create a list of standard words
366 // Get all 'sets' of words
367 // Generate a complete list of all
369 foreach($aPhrases as $iPhrase => $sPhrase)
371 $aPhrase = $oDB->getRow("select make_standard_name('".pg_escape_string($sPhrase)."') as string");
372 if (PEAR::isError($aPhrase))
374 userError("Illegal query string (not an UTF-8 string): ".$sPhrase);
375 if (CONST_Debug) var_dump($aPhrase);
378 if (trim($aPhrase['string']))
380 $aPhrases[$iPhrase] = $aPhrase;
381 $aPhrases[$iPhrase]['words'] = explode(' ',$aPhrases[$iPhrase]['string']);
382 $aPhrases[$iPhrase]['wordsets'] = getWordSets($aPhrases[$iPhrase]['words'], 0);
383 $aTokens = array_merge($aTokens, getTokensFromSets($aPhrases[$iPhrase]['wordsets']));
387 unset($aPhrases[$iPhrase]);
391 // reindex phrases - we make assumptions later on
392 $aPhraseTypes = array_keys($aPhrases);
393 $aPhrases = array_values($aPhrases);
395 if (sizeof($aTokens))
398 // Check which tokens we have, get the ID numbers
399 $sSQL = 'select word_id,word_token, word, class, type, country_code, operator, search_name_count';
400 $sSQL .= ' from word where word_token in ('.join(',',array_map("getDBQuoted",$aTokens)).')';
401 //$sSQL .= ' and search_name_count < '.CONST_Max_Word_Frequency;
402 //$sSQL .= ' group by word_token, word, class, type, country_code';
404 if (CONST_Debug) var_Dump($sSQL);
406 $aValidTokens = array();
407 if (sizeof($aTokens)) $aDatabaseWords = $oDB->getAll($sSQL);
408 else $aDatabaseWords = array();
409 if (PEAR::IsError($aDatabaseWords))
411 failInternalError("Could not get word tokens.", $sSQL, $aDatabaseWords);
413 $aPossibleMainWordIDs = array();
414 $aWordFrequencyScores = array();
415 foreach($aDatabaseWords as $aToken)
417 // Very special case - require 2 letter country param to match the country code found
418 if ($bStructuredPhrases && $aToken['country_code'] && !empty($aStructuredQuery['country'])
419 && strlen($aStructuredQuery['country']) == 2 && strtolower($aStructuredQuery['country']) != $aToken['country_code'])
424 if (isset($aValidTokens[$aToken['word_token']]))
426 $aValidTokens[$aToken['word_token']][] = $aToken;
430 $aValidTokens[$aToken['word_token']] = array($aToken);
432 if (!$aToken['class'] && !$aToken['country_code']) $aPossibleMainWordIDs[$aToken['word_id']] = 1;
433 $aWordFrequencyScores[$aToken['word_id']] = $aToken['search_name_count'] + 1;
435 if (CONST_Debug) var_Dump($aPhrases, $aValidTokens);
437 // Try and calculate GB postcodes we might be missing
438 foreach($aTokens as $sToken)
440 // Source of gb postcodes is now definitive - always use
441 if (preg_match('/^([A-Z][A-Z]?[0-9][0-9A-Z]? ?[0-9])([A-Z][A-Z])$/', strtoupper(trim($sToken)), $aData))
443 if (substr($aData[1],-2,1) != ' ')
445 $aData[0] = substr($aData[0],0,strlen($aData[1]-1)).' '.substr($aData[0],strlen($aData[1]-1));
446 $aData[1] = substr($aData[1],0,-1).' '.substr($aData[1],-1,1);
448 $aGBPostcodeLocation = gbPostcodeCalculate($aData[0], $aData[1], $aData[2], $oDB);
449 if ($aGBPostcodeLocation)
451 $aValidTokens[$sToken] = $aGBPostcodeLocation;
454 // US ZIP+4 codes - if there is no token,
455 // merge in the 5-digit ZIP code
456 else if (!isset($aValidTokens[$sToken]) && preg_match('/^([0-9]{5}) [0-9]{4}$/', $sToken, $aData))
458 if (isset($aValidTokens[$aData[1]]))
460 foreach($aValidTokens[$aData[1]] as $aToken)
462 if (!$aToken['class'])
464 if (isset($aValidTokens[$sToken]))
466 $aValidTokens[$sToken][] = $aToken;
470 $aValidTokens[$sToken] = array($aToken);
478 foreach($aTokens as $sToken)
480 // Unknown single word token with a number - assume it is a house number
481 if (!isset($aValidTokens[' '.$sToken]) && strpos($sToken,' ') === false && preg_match('/[0-9]/', $sToken))
483 $aValidTokens[' '.$sToken] = array(array('class'=>'place','type'=>'house'));
487 // Any words that have failed completely?
490 // Start the search process
491 $aResultPlaceIDs = array();
494 Calculate all searches using aValidTokens i.e.
495 'Wodsworth Road, Sheffield' =>
499 0 1 (wodsworth)(road)
502 Score how good the search is so they can be ordered
504 foreach($aPhrases as $iPhrase => $sPhrase)
506 $aNewPhraseSearches = array();
507 if ($bStructuredPhrases) $sPhraseType = $aPhraseTypes[$iPhrase];
508 else $sPhraseType = '';
510 foreach($aPhrases[$iPhrase]['wordsets'] as $aWordset)
512 $aWordsetSearches = $aSearches;
514 // Add all words from this wordset
515 foreach($aWordset as $iToken => $sToken)
517 //echo "<br><b>$sToken</b>";
518 $aNewWordsetSearches = array();
520 foreach($aWordsetSearches as $aCurrentSearch)
523 //var_dump($aCurrentSearch);
526 // If the token is valid
527 if (isset($aValidTokens[' '.$sToken]))
529 foreach($aValidTokens[' '.$sToken] as $aSearchTerm)
531 $aSearch = $aCurrentSearch;
532 $aSearch['iSearchRank']++;
533 if (($sPhraseType == '' || $sPhraseType == 'country') && !empty($aSearchTerm['country_code']) && $aSearchTerm['country_code'] != '0')
535 if ($aSearch['sCountryCode'] === false)
537 $aSearch['sCountryCode'] = strtolower($aSearchTerm['country_code']);
538 // Country is almost always at the end of the string - increase score for finding it anywhere else (optimisation)
539 // If reverse order is enabled, it may appear at the beginning as well.
540 if (($iToken+1 != sizeof($aWordset) || $iPhrase+1 != sizeof($aPhrases)) &&
541 (!$bReverseInPlan || $iToken > 0 || $iPhrase > 0))
543 $aSearch['iSearchRank'] += 5;
545 if ($aSearch['iSearchRank'] < $iMaxRank) $aNewWordsetSearches[] = $aSearch;
548 elseif (isset($aSearchTerm['lat']) && $aSearchTerm['lat'] !== '' && $aSearchTerm['lat'] !== null)
550 if ($aSearch['fLat'] === '')
552 $aSearch['fLat'] = $aSearchTerm['lat'];
553 $aSearch['fLon'] = $aSearchTerm['lon'];
554 $aSearch['fRadius'] = $aSearchTerm['radius'];
555 if ($aSearch['iSearchRank'] < $iMaxRank) $aNewWordsetSearches[] = $aSearch;
558 elseif ($sPhraseType == 'postalcode')
560 // We need to try the case where the postal code is the primary element (i.e. no way to tell if it is (postalcode, city) OR (city, postalcode) so try both
561 if (isset($aSearchTerm['word_id']) && $aSearchTerm['word_id'])
563 // If we already have a name try putting the postcode first
564 if (sizeof($aSearch['aName']))
566 $aNewSearch = $aSearch;
567 $aNewSearch['aAddress'] = array_merge($aNewSearch['aAddress'], $aNewSearch['aName']);
568 $aNewSearch['aName'] = array();
569 $aNewSearch['aName'][$aSearchTerm['word_id']] = $aSearchTerm['word_id'];
570 if ($aSearch['iSearchRank'] < $iMaxRank) $aNewWordsetSearches[] = $aNewSearch;
573 if (sizeof($aSearch['aName']))
575 if ((!$bStructuredPhrases || $iPhrase > 0) && $sPhraseType != 'country' && (!isset($aValidTokens[$sToken]) || strlen($sToken) < 4 || strpos($sToken, ' ') !== false))
577 $aSearch['aAddress'][$aSearchTerm['word_id']] = $aSearchTerm['word_id'];
581 $aCurrentSearch['aFullNameAddress'][$aSearchTerm['word_id']] = $aSearchTerm['word_id'];
582 $aSearch['iSearchRank'] += 1000; // skip;
587 $aSearch['aName'][$aSearchTerm['word_id']] = $aSearchTerm['word_id'];
588 //$aSearch['iNamePhrase'] = $iPhrase;
590 if ($aSearch['iSearchRank'] < $iMaxRank) $aNewWordsetSearches[] = $aSearch;
594 elseif (($sPhraseType == '' || $sPhraseType == 'street') && $aSearchTerm['class'] == 'place' && $aSearchTerm['type'] == 'house')
596 if ($aSearch['sHouseNumber'] === '')
598 $aSearch['sHouseNumber'] = $sToken;
599 if ($aSearch['iSearchRank'] < $iMaxRank) $aNewWordsetSearches[] = $aSearch;
601 // Fall back to not searching for this item (better than nothing)
602 $aSearch = $aCurrentSearch;
603 $aSearch['iSearchRank'] += 1;
604 if ($aSearch['iSearchRank'] < $iMaxRank) $aNewWordsetSearches[] = $aSearch;
608 elseif ($sPhraseType == '' && $aSearchTerm['class'] !== '' && $aSearchTerm['class'] !== null)
610 if ($aSearch['sClass'] === '')
612 $aSearch['sOperator'] = $aSearchTerm['operator'];
613 $aSearch['sClass'] = $aSearchTerm['class'];
614 $aSearch['sType'] = $aSearchTerm['type'];
615 if (sizeof($aSearch['aName'])) $aSearch['sOperator'] = 'name';
616 else $aSearch['sOperator'] = 'near'; // near = in for the moment
618 // Do we have a shortcut id?
619 if ($aSearch['sOperator'] == 'name')
621 $sSQL = "select get_tagpair('".$aSearch['sClass']."', '".$aSearch['sType']."')";
622 if ($iAmenityID = $oDB->getOne($sSQL))
624 $aValidTokens[$aSearch['sClass'].':'.$aSearch['sType']] = array('word_id' => $iAmenityID);
625 $aSearch['aName'][$iAmenityID] = $iAmenityID;
626 $aSearch['sClass'] = '';
627 $aSearch['sType'] = '';
630 if ($aSearch['iSearchRank'] < $iMaxRank) $aNewWordsetSearches[] = $aSearch;
633 elseif (isset($aSearchTerm['word_id']) && $aSearchTerm['word_id'])
635 if (sizeof($aSearch['aName']))
637 if ((!$bStructuredPhrases || $iPhrase > 0) && $sPhraseType != 'country' && (!isset($aValidTokens[$sToken]) || strlen($sToken) < 4 || strpos($sToken, ' ') !== false))
639 $aSearch['aAddress'][$aSearchTerm['word_id']] = $aSearchTerm['word_id'];
643 $aCurrentSearch['aFullNameAddress'][$aSearchTerm['word_id']] = $aSearchTerm['word_id'];
644 $aSearch['iSearchRank'] += 1000; // skip;
649 $aSearch['aName'][$aSearchTerm['word_id']] = $aSearchTerm['word_id'];
650 //$aSearch['iNamePhrase'] = $iPhrase;
652 if ($aSearch['iSearchRank'] < $iMaxRank) $aNewWordsetSearches[] = $aSearch;
656 if (isset($aValidTokens[$sToken]))
658 // Allow searching for a word - but at extra cost
659 foreach($aValidTokens[$sToken] as $aSearchTerm)
661 if (isset($aSearchTerm['word_id']) && $aSearchTerm['word_id'])
663 if ((!$bStructuredPhrases || $iPhrase > 0) && sizeof($aCurrentSearch['aName']) && strlen($sToken) >= 4)
665 $aSearch = $aCurrentSearch;
666 $aSearch['iSearchRank'] += 1;
667 if ($aWordFrequencyScores[$aSearchTerm['word_id']] < CONST_Max_Word_Frequency)
669 $aSearch['aAddress'][$aSearchTerm['word_id']] = $aSearchTerm['word_id'];
670 if ($aSearch['iSearchRank'] < $iMaxRank) $aNewWordsetSearches[] = $aSearch;
672 elseif (isset($aValidTokens[' '.$sToken])) // revert to the token version?
674 foreach($aValidTokens[' '.$sToken] as $aSearchTermToken)
676 if (empty($aSearchTermToken['country_code'])
677 && empty($aSearchTermToken['lat'])
678 && empty($aSearchTermToken['class']))
680 $aSearch = $aCurrentSearch;
681 $aSearch['iSearchRank'] += 1;
682 $aSearch['aAddress'][$aSearchTermToken['word_id']] = $aSearchTermToken['word_id'];
683 if ($aSearch['iSearchRank'] < $iMaxRank) $aNewWordsetSearches[] = $aSearch;
689 $aSearch['aAddressNonSearch'][$aSearchTerm['word_id']] = $aSearchTerm['word_id'];
690 if ($aSearch['iSearchRank'] < $iMaxRank) $aNewWordsetSearches[] = $aSearch;
694 if (!sizeof($aCurrentSearch['aName']) || $aCurrentSearch['iNamePhrase'] == $iPhrase)
696 $aSearch = $aCurrentSearch;
697 $aSearch['iSearchRank'] += 2;
698 if (preg_match('#^[0-9]+$#', $sToken)) $aSearch['iSearchRank'] += 2;
699 if ($aWordFrequencyScores[$aSearchTerm['word_id']] < CONST_Max_Word_Frequency)
700 $aSearch['aName'][$aSearchTerm['word_id']] = $aSearchTerm['word_id'];
702 $aSearch['aNameNonSearch'][$aSearchTerm['word_id']] = $aSearchTerm['word_id'];
703 $aSearch['iNamePhrase'] = $iPhrase;
704 if ($aSearch['iSearchRank'] < $iMaxRank) $aNewWordsetSearches[] = $aSearch;
711 // Allow skipping a word - but at EXTREAM cost
712 //$aSearch = $aCurrentSearch;
713 //$aSearch['iSearchRank']+=100;
714 //$aNewWordsetSearches[] = $aSearch;
718 usort($aNewWordsetSearches, 'bySearchRank');
719 $aWordsetSearches = array_slice($aNewWordsetSearches, 0, 50);
721 //var_Dump('<hr>',sizeof($aWordsetSearches)); exit;
723 $aNewPhraseSearches = array_merge($aNewPhraseSearches, $aNewWordsetSearches);
724 usort($aNewPhraseSearches, 'bySearchRank');
726 $aSearchHash = array();
727 foreach($aNewPhraseSearches as $iSearch => $aSearch)
729 $sHash = serialize($aSearch);
730 if (isset($aSearchHash[$sHash])) unset($aNewPhraseSearches[$iSearch]);
731 else $aSearchHash[$sHash] = 1;
734 $aNewPhraseSearches = array_slice($aNewPhraseSearches, 0, 50);
737 // Re-group the searches by their score, junk anything over 20 as just not worth trying
738 $aGroupedSearches = array();
739 foreach($aNewPhraseSearches as $aSearch)
741 if ($aSearch['iSearchRank'] < $iMaxRank)
743 if (!isset($aGroupedSearches[$aSearch['iSearchRank']])) $aGroupedSearches[$aSearch['iSearchRank']] = array();
744 $aGroupedSearches[$aSearch['iSearchRank']][] = $aSearch;
747 ksort($aGroupedSearches);
750 $aSearches = array();
751 foreach($aGroupedSearches as $iScore => $aNewSearches)
753 $iSearchCount += sizeof($aNewSearches);
754 $aSearches = array_merge($aSearches, $aNewSearches);
755 if ($iSearchCount > 50) break;
758 //if (CONST_Debug) _debugDumpGroupedSearches($aGroupedSearches, $aValidTokens);
765 // Re-group the searches by their score, junk anything over 20 as just not worth trying
766 $aGroupedSearches = array();
767 foreach($aSearches as $aSearch)
769 if ($aSearch['iSearchRank'] < $iMaxRank)
771 if (!isset($aGroupedSearches[$aSearch['iSearchRank']])) $aGroupedSearches[$aSearch['iSearchRank']] = array();
772 $aGroupedSearches[$aSearch['iSearchRank']][] = $aSearch;
775 ksort($aGroupedSearches);
778 if (CONST_Debug) var_Dump($aGroupedSearches);
782 $aCopyGroupedSearches = $aGroupedSearches;
783 foreach($aCopyGroupedSearches as $iGroup => $aSearches)
785 foreach($aSearches as $iSearch => $aSearch)
787 if (sizeof($aSearch['aAddress']))
789 $iReverseItem = array_pop($aSearch['aAddress']);
790 if (isset($aPossibleMainWordIDs[$iReverseItem]))
792 $aSearch['aAddress'] = array_merge($aSearch['aAddress'], $aSearch['aName']);
793 $aSearch['aName'] = array($iReverseItem);
794 $aGroupedSearches[$iGroup][] = $aSearch;
796 //$aReverseSearch['aName'][$iReverseItem] = $iReverseItem;
797 //$aGroupedSearches[$iGroup][] = $aReverseSearch;
803 if (CONST_Search_TryDroppedAddressTerms && sizeof($aStructuredQuery) > 0)
805 $aCopyGroupedSearches = $aGroupedSearches;
806 foreach($aCopyGroupedSearches as $iGroup => $aSearches)
808 foreach($aSearches as $iSearch => $aSearch)
810 $aReductionsList = array($aSearch['aAddress']);
811 $iSearchRank = $aSearch['iSearchRank'];
812 while(sizeof($aReductionsList) > 0)
815 if ($iSearchRank > iMaxRank) break 3;
816 $aNewReductionsList = array();
817 foreach($aReductionsList as $aReductionsWordList)
819 for ($iReductionWord = 0; $iReductionWord < sizeof($aReductionsWordList); $iReductionWord++)
821 $aReductionsWordListResult = array_merge(array_slice($aReductionsWordList, 0, $iReductionWord), array_slice($aReductionsWordList, $iReductionWord+1));
822 $aReverseSearch = $aSearch;
823 $aSearch['aAddress'] = $aReductionsWordListResult;
824 $aSearch['iSearchRank'] = $iSearchRank;
825 $aGroupedSearches[$iSearchRank][] = $aReverseSearch;
826 if (sizeof($aReductionsWordListResult) > 0)
828 $aNewReductionsList[] = $aReductionsWordListResult;
832 $aReductionsList = $aNewReductionsList;
836 ksort($aGroupedSearches);
839 // Filter out duplicate searches
840 $aSearchHash = array();
841 foreach($aGroupedSearches as $iGroup => $aSearches)
843 foreach($aSearches as $iSearch => $aSearch)
845 $sHash = serialize($aSearch);
846 if (isset($aSearchHash[$sHash]))
848 unset($aGroupedSearches[$iGroup][$iSearch]);
849 if (sizeof($aGroupedSearches[$iGroup]) == 0) unset($aGroupedSearches[$iGroup]);
853 $aSearchHash[$sHash] = 1;
858 if (CONST_Debug) _debugDumpGroupedSearches($aGroupedSearches, $aValidTokens);
862 foreach($aGroupedSearches as $iGroupedRank => $aSearches)
865 foreach($aSearches as $aSearch)
869 if (CONST_Debug) { echo "<hr><b>Search Loop, group $iGroupLoop, loop $iQueryLoop</b>"; }
870 if (CONST_Debug) _debugDumpGroupedSearches(array($iGroupedRank => array($aSearch)), $aValidTokens);
873 // Must have a location term
874 if (!sizeof($aSearch['aName']) && !sizeof($aSearch['aAddress']) && !$aSearch['fLon'])
876 if ($aSearch['sCountryCode'] && !$aSearch['sClass'] && !$aSearch['sHouseNumber'])
878 if (4 >= $iMinAddressRank && 4 <= $iMaxAddressRank)
880 $sSQL = "select place_id from placex where calculated_country_code='".$aSearch['sCountryCode']."' and rank_search = 4";
881 if ($sCountryCodesSQL) $sSQL .= " and calculated_country_code in ($sCountryCodesSQL)";
882 $sSQL .= " order by st_area(geometry) desc limit 1";
883 if (CONST_Debug) var_dump($sSQL);
884 $aPlaceIDs = $oDB->getCol($sSQL);
889 if (!$bBoundingBoxSearch && !$aSearch['fLon']) continue;
890 if (!$aSearch['sClass']) continue;
891 $sSQL = "select count(*) from pg_tables where tablename = 'place_classtype_".$aSearch['sClass']."_".$aSearch['sType']."'";
892 if ($oDB->getOne($sSQL))
894 $sSQL = "select place_id from place_classtype_".$aSearch['sClass']."_".$aSearch['sType']." ct";
895 if ($sCountryCodesSQL) $sSQL .= " join placex using (place_id)";
896 $sSQL .= " where st_contains($sViewboxSmallSQL, ct.centroid)";
897 if ($sCountryCodesSQL) $sSQL .= " and calculated_country_code in ($sCountryCodesSQL)";
898 if (sizeof($aExcludePlaceIDs))
900 $sSQL .= " and place_id not in (".join(',',$aExcludePlaceIDs).")";
902 if ($sViewboxCentreSQL) $sSQL .= " order by st_distance($sViewboxCentreSQL, ct.centroid) asc";
903 $sSQL .= " limit $iLimit";
904 if (CONST_Debug) var_dump($sSQL);
905 $aPlaceIDs = $oDB->getCol($sSQL);
907 // If excluded place IDs are given, it is fair to assume that
908 // there have been results in the small box, so no further
909 // expansion in that case.
910 if (!sizeof($aPlaceIDs) && !sizeof($aExcludePlaceIDs))
912 $sSQL = "select place_id from place_classtype_".$aSearch['sClass']."_".$aSearch['sType']." ct";
913 if ($sCountryCodesSQL) $sSQL .= " join placex using (place_id)";
914 $sSQL .= " where st_contains($sViewboxLargeSQL, ct.centroid)";
915 if ($sCountryCodesSQL) $sSQL .= " and calculated_country_code in ($sCountryCodesSQL)";
916 if ($sViewboxCentreSQL) $sSQL .= " order by st_distance($sViewboxCentreSQL, ct.centroid) asc";
917 $sSQL .= " limit $iLimit";
918 if (CONST_Debug) var_dump($sSQL);
919 $aPlaceIDs = $oDB->getCol($sSQL);
924 $sSQL = "select place_id from placex where class='".$aSearch['sClass']."' and type='".$aSearch['sType']."'";
925 $sSQL .= " and st_contains($sViewboxSmallSQL, geometry) and linked_place_id is null";
926 if ($sCountryCodesSQL) $sSQL .= " and calculated_country_code in ($sCountryCodesSQL)";
927 if ($sViewboxCentreSQL) $sSQL .= " order by st_distance($sViewboxCentreSQL, centroid) asc";
928 $sSQL .= " limit $iLimit";
929 if (CONST_Debug) var_dump($sSQL);
930 $aPlaceIDs = $oDB->getCol($sSQL);
936 $aPlaceIDs = array();
938 // First we need a position, either aName or fLat or both
942 // TODO: filter out the pointless search terms (2 letter name tokens and less)
943 // they might be right - but they are just too darned expensive to run
944 if (sizeof($aSearch['aName'])) $aTerms[] = "name_vector @> ARRAY[".join($aSearch['aName'],",")."]";
945 if (sizeof($aSearch['aNameNonSearch'])) $aTerms[] = "array_cat(name_vector,ARRAY[]::integer[]) @> ARRAY[".join($aSearch['aNameNonSearch'],",")."]";
946 if (sizeof($aSearch['aAddress']) && $aSearch['aName'] != $aSearch['aAddress'])
948 // For infrequent name terms disable index usage for address
949 if (CONST_Search_NameOnlySearchFrequencyThreshold &&
950 sizeof($aSearch['aName']) == 1 &&
951 $aWordFrequencyScores[$aSearch['aName'][reset($aSearch['aName'])]] < CONST_Search_NameOnlySearchFrequencyThreshold)
953 $aTerms[] = "array_cat(nameaddress_vector,ARRAY[]::integer[]) @> ARRAY[".join(array_merge($aSearch['aAddress'],$aSearch['aAddressNonSearch']),",")."]";
957 $aTerms[] = "nameaddress_vector @> ARRAY[".join($aSearch['aAddress'],",")."]";
958 if (sizeof($aSearch['aAddressNonSearch'])) $aTerms[] = "array_cat(nameaddress_vector,ARRAY[]::integer[]) @> ARRAY[".join($aSearch['aAddressNonSearch'],",")."]";
961 if ($aSearch['sCountryCode']) $aTerms[] = "country_code = '".pg_escape_string($aSearch['sCountryCode'])."'";
962 if ($aSearch['sHouseNumber']) $aTerms[] = "address_rank between 16 and 27";
963 if ($aSearch['fLon'] && $aSearch['fLat'])
965 $aTerms[] = "ST_DWithin(centroid, ST_SetSRID(ST_Point(".$aSearch['fLon'].",".$aSearch['fLat']."),4326), ".$aSearch['fRadius'].")";
966 $aOrder[] = "ST_Distance(centroid, ST_SetSRID(ST_Point(".$aSearch['fLon'].",".$aSearch['fLat']."),4326)) ASC";
968 if (sizeof($aExcludePlaceIDs))
970 $aTerms[] = "place_id not in (".join(',',$aExcludePlaceIDs).")";
972 if ($sCountryCodesSQL)
974 $aTerms[] = "country_code in ($sCountryCodesSQL)";
977 if ($bBoundingBoxSearch) $aTerms[] = "centroid && $sViewboxSmallSQL";
978 if ($sNearPointSQL) $aOrder[] = "ST_Distance($sNearPointSQL, centroid) asc";
980 $sImportanceSQL = '(case when importance = 0 OR importance IS NULL then 0.75-(search_rank::float/40) else importance end)';
981 if ($sViewboxSmallSQL) $sImportanceSQL .= " * case when ST_Contains($sViewboxSmallSQL, centroid) THEN 1 ELSE 0.5 END";
982 if ($sViewboxLargeSQL) $sImportanceSQL .= " * case when ST_Contains($sViewboxLargeSQL, centroid) THEN 1 ELSE 0.5 END";
983 $aOrder[] = "$sImportanceSQL DESC";
984 if (sizeof($aSearch['aFullNameAddress']))
986 $aOrder[] = '(select count(*) from (select unnest(ARRAY['.join($aSearch['aFullNameAddress'],",").']) INTERSECT select unnest(nameaddress_vector))s) DESC';
991 $sSQL = "select place_id";
992 $sSQL .= " from search_name";
993 $sSQL .= " where ".join(' and ',$aTerms);
994 $sSQL .= " order by ".join(', ',$aOrder);
995 if ($aSearch['sHouseNumber'] || $aSearch['sClass'])
996 $sSQL .= " limit 50";
997 elseif (!sizeof($aSearch['aName']) && !sizeof($aSearch['aAddress']) && $aSearch['sClass'])
1000 $sSQL .= " limit ".$iLimit;
1002 if (CONST_Debug) { var_dump($sSQL); }
1003 $aViewBoxPlaceIDs = $oDB->getAll($sSQL);
1004 if (PEAR::IsError($aViewBoxPlaceIDs))
1006 failInternalError("Could not get places for search terms.", $sSQL, $aViewBoxPlaceIDs);
1008 //var_dump($aViewBoxPlaceIDs);
1009 // Did we have an viewbox matches?
1010 $aPlaceIDs = array();
1011 $bViewBoxMatch = false;
1012 foreach($aViewBoxPlaceIDs as $aViewBoxRow)
1014 //if ($bViewBoxMatch == 1 && $aViewBoxRow['in_small'] == 'f') break;
1015 //if ($bViewBoxMatch == 2 && $aViewBoxRow['in_large'] == 'f') break;
1016 //if ($aViewBoxRow['in_small'] == 't') $bViewBoxMatch = 1;
1017 //else if ($aViewBoxRow['in_large'] == 't') $bViewBoxMatch = 2;
1018 $aPlaceIDs[] = $aViewBoxRow['place_id'];
1021 //var_Dump($aPlaceIDs);
1024 if ($aSearch['sHouseNumber'] && sizeof($aPlaceIDs))
1026 $aRoadPlaceIDs = $aPlaceIDs;
1027 $sPlaceIDs = join(',',$aPlaceIDs);
1029 // Now they are indexed look for a house attached to a street we found
1030 $sHouseNumberRegex = '\\\\m'.str_replace(' ','[-,/ ]',$aSearch['sHouseNumber']).'\\\\M';
1031 $sSQL = "select place_id from placex where parent_place_id in (".$sPlaceIDs.") and housenumber ~* E'".$sHouseNumberRegex."'";
1032 if (sizeof($aExcludePlaceIDs))
1034 $sSQL .= " and place_id not in (".join(',',$aExcludePlaceIDs).")";
1036 $sSQL .= " limit $iLimit";
1037 if (CONST_Debug) var_dump($sSQL);
1038 $aPlaceIDs = $oDB->getCol($sSQL);
1040 // If not try the aux fallback table
1042 if (!sizeof($aPlaceIDs))
1044 $sSQL = "select place_id from location_property_aux where parent_place_id in (".$sPlaceIDs.") and housenumber = '".pg_escape_string($aSearch['sHouseNumber'])."'";
1045 if (sizeof($aExcludePlaceIDs))
1047 $sSQL .= " and place_id not in (".join(',',$aExcludePlaceIDs).")";
1049 //$sSQL .= " limit $iLimit";
1050 if (CONST_Debug) var_dump($sSQL);
1051 $aPlaceIDs = $oDB->getCol($sSQL);
1055 if (!sizeof($aPlaceIDs))
1057 $sSQL = "select place_id from location_property_tiger where parent_place_id in (".$sPlaceIDs.") and housenumber = '".pg_escape_string($aSearch['sHouseNumber'])."'";
1058 if (sizeof($aExcludePlaceIDs))
1060 $sSQL .= " and place_id not in (".join(',',$aExcludePlaceIDs).")";
1062 //$sSQL .= " limit $iLimit";
1063 if (CONST_Debug) var_dump($sSQL);
1064 $aPlaceIDs = $oDB->getCol($sSQL);
1067 // Fallback to the road
1068 if (!sizeof($aPlaceIDs) && preg_match('/[0-9]+/', $aSearch['sHouseNumber']))
1070 $aPlaceIDs = $aRoadPlaceIDs;
1075 if ($aSearch['sClass'] && sizeof($aPlaceIDs))
1077 $sPlaceIDs = join(',',$aPlaceIDs);
1078 $aClassPlaceIDs = array();
1080 if (!$aSearch['sOperator'] || $aSearch['sOperator'] == 'name')
1082 // If they were searching for a named class (i.e. 'Kings Head pub') then we might have an extra match
1083 $sSQL = "select place_id from placex where place_id in ($sPlaceIDs) and class='".$aSearch['sClass']."' and type='".$aSearch['sType']."'";
1084 $sSQL .= " and linked_place_id is null";
1085 if ($sCountryCodesSQL) $sSQL .= " and calculated_country_code in ($sCountryCodesSQL)";
1086 $sSQL .= " order by rank_search asc limit $iLimit";
1087 if (CONST_Debug) var_dump($sSQL);
1088 $aClassPlaceIDs = $oDB->getCol($sSQL);
1091 if (!$aSearch['sOperator'] || $aSearch['sOperator'] == 'near') // & in
1093 $sSQL = "select count(*) from pg_tables where tablename = 'place_classtype_".$aSearch['sClass']."_".$aSearch['sType']."'";
1094 $bCacheTable = $oDB->getOne($sSQL);
1096 $sSQL = "select min(rank_search) from placex where place_id in ($sPlaceIDs)";
1098 if (CONST_Debug) var_dump($sSQL);
1099 $iMaxRank = ((int)$oDB->getOne($sSQL));
1101 // For state / country level searches the normal radius search doesn't work very well
1102 $sPlaceGeom = false;
1103 if ($iMaxRank < 9 && $bCacheTable)
1105 // Try and get a polygon to search in instead
1106 $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";
1107 if (CONST_Debug) var_dump($sSQL);
1108 $sPlaceGeom = $oDB->getOne($sSQL);
1118 $sSQL = "select place_id from placex where place_id in ($sPlaceIDs) and rank_search < $iMaxRank";
1119 if (CONST_Debug) var_dump($sSQL);
1120 $aPlaceIDs = $oDB->getCol($sSQL);
1121 $sPlaceIDs = join(',',$aPlaceIDs);
1124 if ($sPlaceIDs || $sPlaceGeom)
1130 // More efficient - can make the range bigger
1134 if ($sNearPointSQL) $sOrderBySQL = "ST_Distance($sNearPointSQL, l.centroid)";
1135 else if ($sPlaceIDs) $sOrderBySQL = "ST_Distance(l.centroid, f.geometry)";
1136 else if ($sPlaceGeom) $sOrderBysSQL = "ST_Distance(st_centroid('".$sPlaceGeom."'), l.centroid)";
1138 $sSQL = "select distinct l.place_id".($sOrderBySQL?','.$sOrderBySQL:'')." from place_classtype_".$aSearch['sClass']."_".$aSearch['sType']." as l";
1139 if ($sCountryCodesSQL) $sSQL .= " join placex as lp using (place_id)";
1142 $sSQL .= ",placex as f where ";
1143 $sSQL .= "f.place_id in ($sPlaceIDs) and ST_DWithin(l.centroid, f.centroid, $fRange) ";
1148 $sSQL .= "ST_Contains('".$sPlaceGeom."', l.centroid) ";
1150 if (sizeof($aExcludePlaceIDs))
1152 $sSQL .= " and l.place_id not in (".join(',',$aExcludePlaceIDs).")";
1154 if ($sCountryCodesSQL) $sSQL .= " and lp.calculated_country_code in ($sCountryCodesSQL)";
1155 if ($sOrderBySQL) $sSQL .= "order by ".$sOrderBySQL." 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));
1163 if (isset($aSearch['fRadius']) && $aSearch['fRadius']) $fRange = $aSearch['fRadius'];
1166 if ($sNearPointSQL) $sOrderBySQL = "ST_Distance($sNearPointSQL, l.geometry)";
1167 else $sOrderBySQL = "ST_Distance(l.geometry, f.geometry)";
1169 $sSQL = "select distinct l.place_id".($sOrderBysSQL?','.$sOrderBysSQL:'')." from placex as l,placex as f where ";
1170 $sSQL .= "f.place_id in ( $sPlaceIDs) and ST_DWithin(l.geometry, f.centroid, $fRange) ";
1171 $sSQL .= "and l.class='".$aSearch['sClass']."' and l.type='".$aSearch['sType']."' ";
1172 if (sizeof($aExcludePlaceIDs))
1174 $sSQL .= " and l.place_id not in (".join(',',$aExcludePlaceIDs).")";
1176 if ($sCountryCodesSQL) $sSQL .= " and l.calculated_country_code in ($sCountryCodesSQL)";
1177 if ($sOrderBy) $sSQL .= "order by ".$OrderBysSQL." asc";
1178 if ($iOffset) $sSQL .= " offset $iOffset";
1179 $sSQL .= " limit $iLimit";
1180 if (CONST_Debug) var_dump($sSQL);
1181 $aClassPlaceIDs = array_merge($aClassPlaceIDs, $oDB->getCol($sSQL));
1186 $aPlaceIDs = $aClassPlaceIDs;
1192 if (PEAR::IsError($aPlaceIDs))
1194 failInternalError("Could not get place IDs from tokens." ,$sSQL, $aPlaceIDs);
1197 if (CONST_Debug) { echo "<br><b>Place IDs:</b> "; var_Dump($aPlaceIDs); }
1199 foreach($aPlaceIDs as $iPlaceID)
1201 $aResultPlaceIDs[$iPlaceID] = $iPlaceID;
1203 if ($iQueryLoop > 20) break;
1206 if (isset($aResultPlaceIDs) && sizeof($aResultPlaceIDs) && ($iMinAddressRank != 0 || $iMaxAddressRank != 30))
1208 // Need to verify passes rank limits before dropping out of the loop (yuk!)
1209 $sSQL = "select place_id from placex where place_id in (".join(',',$aResultPlaceIDs).") ";
1210 $sSQL .= "and (placex.rank_address between $iMinAddressRank and $iMaxAddressRank ";
1211 if (14 >= $iMinAddressRank && 14 <= $iMaxAddressRank) $sSQL .= " OR (extratags->'place') = 'city'";
1212 if ($aAddressRankList) $sSQL .= " OR placex.rank_address in (".join(',',$aAddressRankList).")";
1213 $sSQL .= ") UNION select place_id from location_property_tiger where place_id in (".join(',',$aResultPlaceIDs).") ";
1214 $sSQL .= "and (30 between $iMinAddressRank and $iMaxAddressRank ";
1215 if ($aAddressRankList) $sSQL .= " OR 30 in (".join(',',$aAddressRankList).")";
1217 if (CONST_Debug) var_dump($sSQL);
1218 $aResultPlaceIDs = $oDB->getCol($sSQL);
1223 if (isset($aResultPlaceIDs) && sizeof($aResultPlaceIDs)) break;
1224 if ($iGroupLoop > 4) break;
1225 if ($iQueryLoop > 30) break;
1228 // Did we find anything?
1229 if (isset($aResultPlaceIDs) && sizeof($aResultPlaceIDs))
1231 //var_Dump($aResultPlaceIDs);exit;
1232 // Get the details for display (is this a redundant extra step?)
1233 $sPlaceIDs = join(',',$aResultPlaceIDs);
1234 $sImportanceSQL = '';
1235 if ($sViewboxSmallSQL) $sImportanceSQL .= " case when ST_Contains($sViewboxSmallSQL, ST_Collect(centroid)) THEN 1 ELSE 0.75 END * ";
1236 if ($sViewboxLargeSQL) $sImportanceSQL .= " case when ST_Contains($sViewboxLargeSQL, ST_Collect(centroid)) THEN 1 ELSE 0.75 END * ";
1238 $sOrderSQL = 'CASE ';
1239 foreach(array_keys($aResultPlaceIDs) as $iOrder => $iPlaceID)
1241 $sOrderSQL .= 'when min(place_id) = '.$iPlaceID.' then '.$iOrder.' ';
1243 $sOrderSQL .= ' ELSE 10000000 END';
1244 $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,";
1245 $sSQL .= "get_address_by_language(place_id, $sLanguagePrefArraySQL) as langaddress,";
1246 $sSQL .= "get_name_by_language(name, $sLanguagePrefArraySQL) as placename,";
1247 $sSQL .= "get_name_by_language(name, ARRAY['ref']) as ref,";
1248 $sSQL .= "avg(ST_X(centroid)) as lon,avg(ST_Y(centroid)) as lat, ";
1249 //$sSQL .= $sOrderSQL." as porder, ";
1250 $sSQL .= $sImportanceSQL."coalesce(importance,0.75-(rank_search::float/40)) as importance, ";
1251 $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, ";
1252 $sSQL .= "(extratags->'place') as extra_place ";
1253 $sSQL .= "from placex where place_id in ($sPlaceIDs) ";
1254 $sSQL .= "and (placex.rank_address between $iMinAddressRank and $iMaxAddressRank ";
1255 if (14 >= $iMinAddressRank && 14 <= $iMaxAddressRank) $sSQL .= " OR (extratags->'place') = 'city'";
1256 if ($aAddressRankList) $sSQL .= " OR placex.rank_address in (".join(',',$aAddressRankList).")";
1258 if ($sAllowedTypesSQLList) $sSQL .= "and placex.class in $sAllowedTypesSQLList ";
1259 $sSQL .= "and linked_place_id is null ";
1260 $sSQL .= "group by osm_type,osm_id,class,type,admin_level,rank_search,rank_address,calculated_country_code,importance";
1261 if (!$bDeDupe) $sSQL .= ",place_id";
1262 $sSQL .= ",langaddress ";
1263 $sSQL .= ",placename ";
1265 $sSQL .= ",extratags->'place' ";
1267 $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,";
1268 $sSQL .= "get_address_by_language(place_id, $sLanguagePrefArraySQL) as langaddress,";
1269 $sSQL .= "null as placename,";
1270 $sSQL .= "null as ref,";
1271 $sSQL .= "avg(ST_X(centroid)) as lon,avg(ST_Y(centroid)) as lat, ";
1272 //$sSQL .= $sOrderSQL." as porder, ";
1273 $sSQL .= $sImportanceSQL."0.015 as importance, ";
1274 $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, ";
1275 $sSQL .= "null as extra_place ";
1276 $sSQL .= "from location_property_tiger where place_id in ($sPlaceIDs) ";
1277 $sSQL .= "and 30 between $iMinAddressRank and $iMaxAddressRank ";
1278 $sSQL .= "group by place_id";
1279 if (!$bDeDupe) $sSQL .= ",place_id";
1282 $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,";
1283 $sSQL .= "get_address_by_language(place_id, $sLanguagePrefArraySQL) as langaddress,";
1284 $sSQL .= "null as placename,";
1285 $sSQL .= "null as ref,";
1286 $sSQL .= "avg(ST_X(centroid)) as lon,avg(ST_Y(centroid)) as lat, ";
1287 //$sSQL .= $sOrderSQL." as porder, ";
1288 $sSQL .= $sImportanceSQL."0.01 as importance, ";
1289 $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, ";
1290 $sSQL .= "null as extra_place ";
1291 $sSQL .= "from location_property_aux where place_id in ($sPlaceIDs) ";
1292 $sSQL .= "and 30 between $iMinAddressRank and $iMaxAddressRank ";
1293 $sSQL .= "group by place_id";
1294 if (!$bDeDupe) $sSQL .= ",place_id";
1295 $sSQL .= ",get_address_by_language(place_id, $sLanguagePrefArraySQL) ";
1296 $sSQL .= "order by importance desc";
1297 //$sSQL .= "order by rank_search,rank_address,porder asc";
1299 if (CONST_Debug) { echo "<hr>"; var_dump($sSQL); }
1300 $aSearchResults = $oDB->getAll($sSQL);
1301 //var_dump($sSQL,$aSearchResults);exit;
1303 if (PEAR::IsError($aSearchResults))
1305 failInternalError("Could not get details for place.", $sSQL, $aSearchResults);
1308 } // end if ($sQuery)
1311 if (isset($_GET['nearlat']) && trim($_GET['nearlat'])!=='' && isset($_GET['nearlon']) && trim($_GET['nearlon']) !== '')
1313 $iPlaceID = geocodeReverse((float)$_GET['nearlat'], (float)$_GET['nearlon']);
1317 $aResultPlaceIDs = array($iPlaceID);
1318 // TODO: this needs refactoring!
1320 // Get the details for display (is this a redundant extra step?)
1321 $sPlaceIDs = join(',',$aResultPlaceIDs);
1322 $sOrderSQL = 'CASE ';
1323 foreach(array_keys($aResultPlaceIDs) as $iOrder => $iPlaceID)
1325 $sOrderSQL .= 'when min(place_id) = '.$iPlaceID.' then '.$iOrder.' ';
1327 $sOrderSQL .= ' ELSE 10000000 END';
1328 $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,";
1329 $sSQL .= "get_address_by_language(place_id, $sLanguagePrefArraySQL) as langaddress,";
1330 $sSQL .= "get_name_by_language(name, $sLanguagePrefArraySQL) as placename,";
1331 $sSQL .= "get_name_by_language(name, ARRAY['ref']) as ref,";
1332 $sSQL .= "avg(ST_X(centroid)) as lon,avg(ST_Y(centroid)) as lat, ";
1333 //$sSQL .= $sOrderSQL." as porder, ";
1334 $sSQL .= "coalesce(importance,0.75-(rank_search::float/40)) as importance, ";
1335 $sSQL .= "(extratags->'place') as extra_place ";
1336 $sSQL .= "from placex where place_id in ($sPlaceIDs) ";
1337 $sSQL .= "and (placex.rank_address between $iMinAddressRank and $iMaxAddressRank ";
1338 if (14 >= $iMinAddressRank && 14 <= $iMaxAddressRank) $sSQL .= " OR (extratags->'place') = 'city'";
1339 if ($aAddressRankList) $sSQL .= " OR placex.rank_address in (".join(',',$aAddressRankList).")";
1341 $sSQL .= "group by osm_type,osm_id,class,type,admin_level,rank_search,rank_address,calculated_country_code,importance";
1342 if (!$bDeDupe) $sSQL .= ",place_id";
1343 $sSQL .= ",get_address_by_language(place_id, $sLanguagePrefArraySQL) ";
1344 $sSQL .= ",get_name_by_language(name, $sLanguagePrefArraySQL) ";
1345 $sSQL .= ",get_name_by_language(name, ARRAY['ref']) ";
1346 $sSQL .= ",extratags->'place' ";
1348 $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,";
1349 $sSQL .= "get_address_by_language(place_id, $sLanguagePrefArraySQL) as langaddress,";
1350 $sSQL .= "null as placename,";
1351 $sSQL .= "null as ref,";
1352 $sSQL .= "avg(ST_X(centroid)) as lon,avg(ST_Y(centroid)) as lat, ";
1353 //$sSQL .= $sOrderSQL." as porder, ";
1354 $sSQL .= "-0.15 as importance, ";
1355 $sSQL .= "null as extra_place ";
1356 $sSQL .= "from location_property_tiger where place_id in ($sPlaceIDs) ";
1357 $sSQL .= "and 30 between $iMinAddressRank and $iMaxAddressRank ";
1358 $sSQL .= "group by place_id";
1359 if (!$bDeDupe) $sSQL .= ",place_id";
1362 $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,";
1363 $sSQL .= "get_address_by_language(place_id, $sLanguagePrefArraySQL) as langaddress,";
1364 $sSQL .= "null as placename,";
1365 $sSQL .= "null as ref,";
1366 $sSQL .= "avg(ST_X(centroid)) as lon,avg(ST_Y(centroid)) as lat, ";
1367 //$sSQL .= $sOrderSQL." as porder, ";
1368 $sSQL .= "-0.10 as importance, ";
1369 $sSQL .= "null as extra_place ";
1370 $sSQL .= "from location_property_aux where place_id in ($sPlaceIDs) ";
1371 $sSQL .= "and 30 between $iMinAddressRank and $iMaxAddressRank ";
1372 $sSQL .= "group by place_id";
1374 if (!$bDeDupe) $sSQL .= ",place_id";
1375 $sSQL .= ",get_address_by_language(place_id, $sLanguagePrefArraySQL) ";
1376 $sSQL .= "order by importance desc";
1377 //$sSQL .= "order by rank_search,rank_address,porder asc";
1378 if (CONST_Debug) { echo "<hr>", var_dump($sSQL); }
1379 $aSearchResults = $oDB->getAll($sSQL);
1380 //var_dump($sSQL,$aSearchResults);exit;
1382 if (PEAR::IsError($aSearchResults))
1384 failInternalError("Could not get details for place (near).", $sSQL, $aSearchResults);
1389 $aSearchResults = array();
1395 $sSearchResult = '';
1396 if (!sizeof($aSearchResults) && isset($_GET['q']) && $_GET['q'])
1398 $sSearchResult = 'No Results Found';
1400 //var_Dump($aSearchResults);
1402 $aClassType = getClassTypesWithImportance();
1403 $aRecheckWords = preg_split('/\b/u',$sQuery);
1404 foreach($aRecheckWords as $i => $sWord)
1406 if (!$sWord) unset($aRecheckWords[$i]);
1408 foreach($aSearchResults as $iResNum => $aResult)
1410 if (CONST_Search_AreaPolygons)
1412 // Get the bounding box and outline polygon
1413 $sSQL = "select place_id,numfeatures,area,outline,";
1414 $sSQL .= "ST_Y(ST_PointN(ST_ExteriorRing(Box2D(outline)),4)) as minlat,ST_Y(ST_PointN(ST_ExteriorRing(Box2D(outline)),2)) as maxlat,";
1415 $sSQL .= "ST_X(ST_PointN(ST_ExteriorRing(Box2D(outline)),1)) as minlon,ST_X(ST_PointN(ST_ExteriorRing(Box2D(outline)),3)) as maxlon,";
1416 $sSQL .= "ST_AsText(outline) as outlinestring from get_place_boundingbox_quick(".$aResult['place_id'].")";
1418 $sSQL = "select place_id,0 as numfeatures,st_area(geometry) as area,";
1419 $sSQL .= "ST_Y(centroid) as centrelat,ST_X(centroid) as centrelon,";
1420 $sSQL .= "ST_Y(ST_PointN(ST_ExteriorRing(Box2D(geometry)),4)) as minlat,ST_Y(ST_PointN(ST_ExteriorRing(Box2D(geometry)),2)) as maxlat,";
1421 $sSQL .= "ST_X(ST_PointN(ST_ExteriorRing(Box2D(geometry)),1)) as minlon,ST_X(ST_PointN(ST_ExteriorRing(Box2D(geometry)),3)) as maxlon";
1422 if ($bAsGeoJSON) $sSQL .= ",ST_AsGeoJSON(geometry) as asgeojson";
1423 if ($bAsKML) $sSQL .= ",ST_AsKML(geometry) as askml";
1424 if ($bAsSVG) $sSQL .= ",ST_AsSVG(geometry) as assvg";
1425 if ($bAsText || $bShowPolygons) $sSQL .= ",ST_AsText(geometry) as astext";
1426 $sSQL .= " from placex where place_id = ".$aResult['place_id'].' and st_geometrytype(Box2D(geometry)) = \'ST_Polygon\'';
1427 $aPointPolygon = $oDB->getRow($sSQL);
1428 if (PEAR::IsError($aPointPolygon))
1430 failInternalError("Could not get outline.", $sSQL, $aPointPolygon);
1432 if ($aPointPolygon['place_id'])
1434 if ($bAsGeoJSON) $aResult['asgeojson'] = $aPointPolygon['asgeojson'];
1435 if ($bAsKML) $aResult['askml'] = $aPointPolygon['askml'];
1436 if ($bAsSVG) $aResult['assvg'] = $aPointPolygon['assvg'];
1437 if ($bAsText) $aResult['astext'] = $aPointPolygon['astext'];
1439 if ($aPointPolygon['centrelon'] !== null && $aPointPolygon['centrelat'] !== null )
1441 $aResult['lat'] = $aPointPolygon['centrelat'];
1442 $aResult['lon'] = $aPointPolygon['centrelon'];
1446 // Translate geometary string to point array
1447 if (preg_match('#POLYGON\\(\\(([- 0-9.,]+)#',$aPointPolygon['astext'],$aMatch))
1449 preg_match_all('/(-?[0-9.]+) (-?[0-9.]+)/',$aMatch[1],$aPolyPoints,PREG_SET_ORDER);
1451 /*elseif (preg_match('#MULTIPOLYGON\\(\\(\\(([- 0-9.,]+)#',$aPointPolygon['astext'],$aMatch))
1453 preg_match_all('/(-?[0-9.]+) (-?[0-9.]+)/',$aMatch[1],$aPolyPoints,PREG_SET_ORDER);
1455 elseif (preg_match('#POINT\\((-?[0-9.]+) (-?[0-9.]+)\\)#',$aPointPolygon['astext'],$aMatch))
1458 $iSteps = ($fRadius * 40000)^2;
1459 $fStepSize = (2*pi())/$iSteps;
1460 $aPolyPoints = array();
1461 for($f = 0; $f < 2*pi(); $f += $fStepSize)
1463 $aPolyPoints[] = array('',$aMatch[1]+($fRadius*sin($f)),$aMatch[2]+($fRadius*cos($f)));
1465 $aPointPolygon['minlat'] = $aPointPolygon['minlat'] - $fRadius;
1466 $aPointPolygon['maxlat'] = $aPointPolygon['maxlat'] + $fRadius;
1467 $aPointPolygon['minlon'] = $aPointPolygon['minlon'] - $fRadius;
1468 $aPointPolygon['maxlon'] = $aPointPolygon['maxlon'] + $fRadius;
1472 // Output data suitable for display (points and a bounding box)
1473 if ($bShowPolygons && isset($aPolyPoints))
1475 $aResult['aPolyPoints'] = array();
1476 foreach($aPolyPoints as $aPoint)
1478 $aResult['aPolyPoints'][] = array($aPoint[1], $aPoint[2]);
1481 $aResult['aBoundingBox'] = array($aPointPolygon['minlat'],$aPointPolygon['maxlat'],$aPointPolygon['minlon'],$aPointPolygon['maxlon']);
1485 if ($aResult['extra_place'] == 'city')
1487 $aResult['class'] = 'place';
1488 $aResult['type'] = 'city';
1489 $aResult['rank_search'] = 16;
1492 if (!isset($aResult['aBoundingBox']))
1495 $fDiameter = 0.0001;
1497 if (isset($aClassType[$aResult['class'].':'.$aResult['type'].':'.$aResult['admin_level']]['defdiameter'])
1498 && $aClassType[$aResult['class'].':'.$aResult['type'].':'.$aResult['admin_level']]['defdiameter'])
1500 $fDiameter = $aClassType[$aResult['class'].':'.$aResult['type'].':'.$aResult['admin_level']]['defzoom'];
1502 elseif (isset($aClassType[$aResult['class'].':'.$aResult['type']]['defdiameter'])
1503 && $aClassType[$aResult['class'].':'.$aResult['type']]['defdiameter'])
1505 $fDiameter = $aClassType[$aResult['class'].':'.$aResult['type']]['defdiameter'];
1507 $fRadius = $fDiameter / 2;
1509 $iSteps = max(8,min(100,$fRadius * 3.14 * 100000));
1510 $fStepSize = (2*pi())/$iSteps;
1511 $aPolyPoints = array();
1512 for($f = 0; $f < 2*pi(); $f += $fStepSize)
1514 $aPolyPoints[] = array('',$aResult['lon']+($fRadius*sin($f)),$aResult['lat']+($fRadius*cos($f)));
1516 $aPointPolygon['minlat'] = $aResult['lat'] - $fRadius;
1517 $aPointPolygon['maxlat'] = $aResult['lat'] + $fRadius;
1518 $aPointPolygon['minlon'] = $aResult['lon'] - $fRadius;
1519 $aPointPolygon['maxlon'] = $aResult['lon'] + $fRadius;
1521 // Output data suitable for display (points and a bounding box)
1524 $aResult['aPolyPoints'] = array();
1525 foreach($aPolyPoints as $aPoint)
1527 $aResult['aPolyPoints'][] = array($aPoint[1], $aPoint[2]);
1530 $aResult['aBoundingBox'] = array($aPointPolygon['minlat'],$aPointPolygon['maxlat'],$aPointPolygon['minlon'],$aPointPolygon['maxlon']);
1533 // Is there an icon set for this type of result?
1534 if (isset($aClassType[$aResult['class'].':'.$aResult['type']]['icon'])
1535 && $aClassType[$aResult['class'].':'.$aResult['type']]['icon'])
1537 $aResult['icon'] = CONST_Website_BaseURL.'images/mapicons/'.$aClassType[$aResult['class'].':'.$aResult['type']]['icon'].'.p.20.png';
1540 if (isset($aClassType[$aResult['class'].':'.$aResult['type']]['label'])
1541 && $aClassType[$aResult['class'].':'.$aResult['type']]['label'])
1543 $aResult['label'] = $aClassType[$aResult['class'].':'.$aResult['type']]['label'];
1546 if ($bShowAddressDetails)
1548 $aResult['address'] = getAddressDetails($oDB, $sLanguagePrefArraySQL, $aResult['place_id'], $aResult['country_code']);
1549 if ($aResult['extra_place'] == 'city' && !isset($aResult['address']['city']))
1551 $aResult['address'] = array_merge(array('city' => array_shift(array_values($aResult['address']))), $aResult['address']);
1554 //var_dump($aResult['address']);
1558 // Adjust importance for the number of exact string matches in the result
1559 $aResult['importance'] = max(0.001,$aResult['importance']);
1561 $sAddress = $aResult['langaddress'];
1562 foreach($aRecheckWords as $i => $sWord)
1564 if (stripos($sAddress, $sWord)!==false) $iCountWords++;
1567 $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
1569 //if (CONST_Debug) var_dump($aResult['class'].':'.$aResult['type'].':'.$aResult['admin_level']);
1571 if (isset($aClassType[$aResult['class'].':'.$aResult['type'].':'.$aResult['admin_level']]['importance'])
1572 && $aClassType[$aResult['class'].':'.$aResult['type'].':'.$aResult['admin_level']]['importance'])
1574 $aResult['importance'] = $aClassType[$aResult['class'].':'.$aResult['type'].':'.$aResult['admin_level']]['importance'];
1576 elseif (isset($aClassType[$aResult['class'].':'.$aResult['type']]['importance'])
1577 && $aClassType[$aResult['class'].':'.$aResult['type']]['importance'])
1579 $aResult['importance'] = $aClassType[$aResult['class'].':'.$aResult['type']]['importance'];
1583 $aResult['importance'] = 1000000000000000;
1586 $aResult['name'] = $aResult['langaddress'];
1587 $aResult['foundorder'] = -$aResult['addressimportance'];
1588 $aSearchResults[$iResNum] = $aResult;
1590 uasort($aSearchResults, 'byImportance');
1592 $aOSMIDDone = array();
1593 $aClassTypeNameDone = array();
1594 $aToFilter = $aSearchResults;
1595 $aSearchResults = array();
1598 foreach($aToFilter as $iResNum => $aResult)
1600 if ($aResult['type'] == 'adminitrative') $aResult['type'] = 'administrative';
1601 $aExcludePlaceIDs[$aResult['place_id']] = $aResult['place_id'];
1604 $fLat = $aResult['lat'];
1605 $fLon = $aResult['lon'];
1606 if (isset($aResult['zoom'])) $iZoom = $aResult['zoom'];
1609 if (!$bDeDupe || (!isset($aOSMIDDone[$aResult['osm_type'].$aResult['osm_id']])
1610 && !isset($aClassTypeNameDone[$aResult['osm_type'].$aResult['class'].$aResult['type'].$aResult['name'].$aResult['admin_level']])))
1612 $aOSMIDDone[$aResult['osm_type'].$aResult['osm_id']] = true;
1613 $aClassTypeNameDone[$aResult['osm_type'].$aResult['class'].$aResult['type'].$aResult['name'].$aResult['admin_level']] = true;
1614 $aSearchResults[] = $aResult;
1617 // Absolute limit on number of results
1618 if (sizeof($aSearchResults) >= $iFinalLimit) break;
1621 $sDataDate = $oDB->getOne("select TO_CHAR(lastimportdate - '2 minutes'::interval,'YYYY/MM/DD HH24:MI')||' GMT' from import_status limit 1");
1623 if (isset($_GET['nearlat']) && isset($_GET['nearlon']))
1625 $sQuery .= ' ['.$_GET['nearlat'].','.$_GET['nearlon'].']';
1630 logEnd($oDB, $hLog, sizeof($aToFilter));
1632 $sMoreURL = CONST_Website_BaseURL.'search?format='.urlencode($sOutputFormat).'&exclude_place_ids='.join(',',$aExcludePlaceIDs);
1633 if (isset($_SERVER["HTTP_ACCEPT_LANGUAGE"])) $sMoreURL .= '&accept-language='.$_SERVER["HTTP_ACCEPT_LANGUAGE"];
1634 if ($bShowPolygons) $sMoreURL .= '&polygon=1';
1635 if ($bShowAddressDetails) $sMoreURL .= '&addressdetails=1';
1636 if (isset($_GET['viewbox']) && $_GET['viewbox']) $sMoreURL .= '&viewbox='.urlencode($_GET['viewbox']);
1637 if (isset($_GET['nearlat']) && isset($_GET['nearlon'])) $sMoreURL .= '&nearlat='.(float)$_GET['nearlat'].'&nearlon='.(float)$_GET['nearlon'];
1638 $sMoreURL .= '&q='.urlencode($sQuery);
1640 if (CONST_Debug) exit;
1642 include(CONST_BasePath.'/lib/template/search-'.$sOutputFormat.'.php');