2 @define('CONST_ConnectionBucket_PageType', 'Search');
4 require_once(dirname(dirname(__FILE__)).'/lib/init-website.php');
5 require_once(CONST_BasePath.'/lib/log.php');
7 ini_set('memory_limit', '200M');
12 $fLat = CONST_Default_Lat;
13 $fLon = CONST_Default_Lon;
14 $iZoom = CONST_Default_Zoom;
15 $bBoundingBoxSearch = isset($_GET['bounded'])?(bool)$_GET['bounded']:false;
16 $sOutputFormat = 'html';
17 $aSearchResults = array();
18 $aExcludePlaceIDs = array();
19 $sCountryCodesSQL = false;
20 $bDeDupe = isset($_GET['dedupe'])?(bool)$_GET['dedupe']:true;
21 $bReverseInPlan = false;
22 $iFinalLimit = isset($_GET['limit'])?(int)$_GET['limit']:10;
23 $iOffset = isset($_GET['offset'])?(int)$_GET['offset']:0;
25 if ($iFinalLimit > 50) $iFinalLimit = 50;
26 $iLimit = $iFinalLimit + min($iFinalLimit, 10);
28 $iMaxAddressRank = 30;
29 $aAddressRankList = array();
30 $sAllowedTypesSQLList = false;
33 if (isset($_GET['format']) && ($_GET['format'] == 'html' || $_GET['format'] == 'xml' || $_GET['format'] == 'json' || $_GET['format'] == 'jsonv2'))
35 $sOutputFormat = $_GET['format'];
38 // Show / use polygons
39 $bShowPolygons = (boolean)isset($_GET['polygon']) && $_GET['polygon'];
40 if ($sOutputFormat == 'html')
42 $bAsText = $bShowPolygons;
43 $bShowPolygons = false;
50 $bAsGeoJSON = (boolean)isset($_GET['polygon_geojson']) && $_GET['polygon_geojson'];
51 $bAsKML = (boolean)isset($_GET['polygon_kml']) && $_GET['polygon_kml'];
52 $bAsSVG = (boolean)isset($_GET['polygon_svg']) && $_GET['polygon_svg'];
53 $bAsText = (boolean)isset($_GET['polygon_text']) && $_GET['polygon_text'];
54 if ((($bShowPolygons?1:0)
59 ) > CONST_PolygonOutput_MaximumTypes)
61 if (CONST_PolygonOutput_MaximumTypes)
63 userError("Select only ".CONST_PolygonOutput_MaximumTypes." polgyon output option");
67 userError("Polygon output is disabled");
73 // Show address breakdown
74 $bShowAddressDetails = isset($_GET['addressdetails']) && $_GET['addressdetails'];
77 $aLangPrefOrder = getPreferredLanguages();
78 if (isset($aLangPrefOrder['name:de'])) $bReverseInPlan = true;
79 if (isset($aLangPrefOrder['name:ru'])) $bReverseInPlan = true;
80 if (isset($aLangPrefOrder['name:ja'])) $bReverseInPlan = true;
82 $sLanguagePrefArraySQL = "ARRAY[".join(',',array_map("getDBQuoted",$aLangPrefOrder))."]";
84 if (isset($_GET['exclude_place_ids']) && $_GET['exclude_place_ids'])
86 foreach(explode(',',$_GET['exclude_place_ids']) as $iExcludedPlaceID)
88 $iExcludedPlaceID = (int)$iExcludedPlaceID;
89 if ($iExcludedPlaceID) $aExcludePlaceIDs[$iExcludedPlaceID] = $iExcludedPlaceID;
93 // Only certain ranks of feature
94 if (isset($_GET['featureType']) && !isset($_GET['featuretype'])) $_GET['featuretype'] = $_GET['featureType'];
96 if (isset($_GET['featuretype']))
98 switch($_GET['featuretype'])
101 $iMinAddressRank = $iMaxAddressRank = 4;
104 $iMinAddressRank = $iMaxAddressRank = 8;
107 $iMinAddressRank = 14;
108 $iMaxAddressRank = 16;
111 $iMinAddressRank = 8;
112 $iMaxAddressRank = 20;
117 if (isset($_GET['countrycodes']))
119 $aCountryCodes = array();
120 foreach(explode(',',$_GET['countrycodes']) as $sCountryCode)
122 if (preg_match('/^[a-zA-Z][a-zA-Z]$/', $sCountryCode))
124 $aCountryCodes[] = "'".strtolower($sCountryCode)."'";
127 $sCountryCodesSQL = join(',', $aCountryCodes);
131 $sQuery = (isset($_GET['q'])?trim($_GET['q']):'');
132 if (!$sQuery && isset($_SERVER['PATH_INFO']) && $_SERVER['PATH_INFO'][0] == '/')
134 $sQuery = substr($_SERVER['PATH_INFO'], 1);
136 // reverse order of '/' separated string
137 $aPhrases = explode('/', $sQuery);
138 $aPhrases = array_reverse($aPhrases);
139 $sQuery = join(', ',$aPhrases);
143 $aStructuredOptions = array(
144 array('amenity', 26, 30, false),
145 array('street', 26, 30, false),
146 array('city', 14, 24, false),
147 array('postalcode', 5, 11, array(5, 11)),
148 array('county', 9, 13, false),
149 array('state', 8, 8, false),
150 array('country', 4, 4, false),
152 $aStructuredQuery = array();
153 $sAllowedTypesSQLList = '';
154 foreach($aStructuredOptions as $aStructuredOption)
156 loadStructuredAddressElement($aStructuredQuery, $iMinAddressRank, $iMaxAddressRank, $aAddressRankList, $_GET, $aStructuredOption[0], $aStructuredOption[1], $aStructuredOption[2], $aStructuredOption[3]);
158 if (sizeof($aStructuredQuery) > 0)
160 $sQuery = join(', ', $aStructuredQuery);
161 if ($iMaxAddressRank < 30)
163 $sAllowedTypesSQLList = '(\'place\',\'boundary\')';
169 $hLog = logStart($oDB, 'search', $sQuery, $aLangPrefOrder);
171 // Hack to make it handle "new york, ny" (and variants) correctly
172 $sQuery = str_ireplace(array('New York, ny','new york, new york', 'New York ny','new york new york'), 'new york city, ny', $sQuery);
173 if (isset($aLangPrefOrder['name:en']))
175 $sQuery = preg_replace('/,\s*il\s*(,|$)/',', illinois\1', $sQuery);
176 $sQuery = preg_replace('/,\s*al\s*(,|$)/',', alabama\1', $sQuery);
177 $sQuery = preg_replace('/,\s*la\s*(,|$)/',', louisiana\1', $sQuery);
180 // If we have a view box create the SQL
181 // Small is the actual view box, Large is double (on each axis) that
182 $sViewboxCentreSQL = $sViewboxSmallSQL = $sViewboxLargeSQL = false;
183 if (isset($_GET['viewboxlbrt']) && $_GET['viewboxlbrt'])
185 $aCoOrdinatesLBRT = explode(',',$_GET['viewboxlbrt']);
186 $_GET['viewbox'] = $aCoOrdinatesLBRT[0].','.$aCoOrdinatesLBRT[3].','.$aCoOrdinatesLBRT[2].','.$aCoOrdinatesLBRT[1];
188 if (isset($_GET['viewbox']) && $_GET['viewbox'])
190 $aCoOrdinates = explode(',',$_GET['viewbox']);
191 $sViewboxSmallSQL = "ST_SetSRID(ST_MakeBox2D(ST_Point(".(float)$aCoOrdinates[0].",".(float)$aCoOrdinates[1]."),ST_Point(".(float)$aCoOrdinates[2].",".(float)$aCoOrdinates[3].")),4326)";
192 $fHeight = $aCoOrdinates[0]-$aCoOrdinates[2];
193 $fWidth = $aCoOrdinates[1]-$aCoOrdinates[3];
194 $aCoOrdinates[0] += $fHeight;
195 $aCoOrdinates[2] -= $fHeight;
196 $aCoOrdinates[1] += $fWidth;
197 $aCoOrdinates[3] -= $fWidth;
198 $sViewboxLargeSQL = "ST_SetSRID(ST_MakeBox2D(ST_Point(".(float)$aCoOrdinates[0].",".(float)$aCoOrdinates[1]."),ST_Point(".(float)$aCoOrdinates[2].",".(float)$aCoOrdinates[3].")),4326)";
202 $bBoundingBoxSearch = false;
204 if (isset($_GET['route']) && $_GET['route'] && isset($_GET['routewidth']) && $_GET['routewidth'])
206 $aPoints = explode(',',$_GET['route']);
207 if (sizeof($aPoints) % 2 != 0)
209 userError("Uneven number of points");
212 $sViewboxCentreSQL = "ST_SetSRID('LINESTRING(";
214 foreach($aPoints as $i => $fPoint)
218 if ($i != 1) $sViewboxCentreSQL .= ",";
219 $sViewboxCentreSQL .= ((float)$fPoint).' '.$fPrevCoord;
223 $fPrevCoord = (float)$fPoint;
226 $sViewboxCentreSQL .= ")'::geometry,4326)";
228 $sSQL = "select st_buffer(".$sViewboxCentreSQL.",".(float)($_GET['routewidth']/69).")";
229 $sViewboxSmallSQL = $oDB->getOne($sSQL);
230 if (PEAR::isError($sViewboxSmallSQL))
232 failInternalError("Could not get small viewbox.", $sSQL, $sViewboxSmallSQL);
234 $sViewboxSmallSQL = "'".$sViewboxSmallSQL."'::geometry";
236 $sSQL = "select st_buffer(".$sViewboxCentreSQL.",".(float)($_GET['routewidth']/30).")";
237 $sViewboxLargeSQL = $oDB->getOne($sSQL);
238 if (PEAR::isError($sViewboxLargeSQL))
240 failInternalError("Could not get large viewbox.", $sSQL, $sViewboxLargeSQL);
242 $sViewboxLargeSQL = "'".$sViewboxLargeSQL."'::geometry";
243 $bBoundingBoxSearch = true;
246 // Do we have anything that looks like a lat/lon pair?
247 if (preg_match('/\\b([NS])[ ]+([0-9]+[0-9.]*)[ ]+([0-9.]+)?[, ]+([EW])[ ]+([0-9]+)[ ]+([0-9]+[0-9.]*)?\\b/', $sQuery, $aData))
249 $fQueryLat = ($aData[1]=='N'?1:-1) * ($aData[2] + $aData[3]/60);
250 $fQueryLon = ($aData[4]=='E'?1:-1) * ($aData[5] + $aData[6]/60);
251 if ($fQueryLat <= 90.1 && $fQueryLat >= -90.1 && $fQueryLon <= 180.1 && $fQueryLon >= -180.1)
253 $_GET['nearlat'] = $fQueryLat;
254 $_GET['nearlon'] = $fQueryLon;
255 $sQuery = trim(str_replace($aData[0], ' ', $sQuery));
258 elseif (preg_match('/\\b([0-9]+)[ ]+([0-9]+[0-9.]*)?[ ]+([NS])[, ]+([0-9]+)[ ]+([0-9]+[0-9.]*)?[ ]+([EW])\\b/', $sQuery, $aData))
260 $fQueryLat = ($aData[3]=='N'?1:-1) * ($aData[1] + $aData[2]/60);
261 $fQueryLon = ($aData[6]=='E'?1:-1) * ($aData[4] + $aData[5]/60);
262 if ($fQueryLat <= 90.1 && $fQueryLat >= -90.1 && $fQueryLon <= 180.1 && $fQueryLon >= -180.1)
264 $_GET['nearlat'] = $fQueryLat;
265 $_GET['nearlon'] = $fQueryLon;
266 $sQuery = trim(str_replace($aData[0], ' ', $sQuery));
269 elseif (preg_match('/(\\[|^|\\b)(-?[0-9]+[0-9.]*)[, ]+(-?[0-9]+[0-9.]*)(\\]|$|\\b)/', $sQuery, $aData))
271 $fQueryLat = $aData[2];
272 $fQueryLon = $aData[3];
273 if ($fQueryLat <= 90.1 && $fQueryLat >= -90.1 && $fQueryLon <= 180.1 && $fQueryLon >= -180.1)
275 $_GET['nearlat'] = $fQueryLat;
276 $_GET['nearlon'] = $fQueryLon;
277 $sQuery = trim(str_replace($aData[0], ' ', $sQuery));
281 if ($sQuery || $aStructuredQuery)
283 // Start with a blank search
285 array('iSearchRank' => 0, 'iNamePhrase' => -1, 'sCountryCode' => false, 'aName'=>array(), 'aAddress'=>array(), 'aFullNameAddress'=>array(),
286 'aNameNonSearch'=>array(), 'aAddressNonSearch'=>array(),
287 'sOperator'=>'', 'aFeatureName' => array(), 'sClass'=>'', 'sType'=>'', 'sHouseNumber'=>'', 'fLat'=>'', 'fLon'=>'', 'fRadius'=>'')
290 $sNearPointSQL = false;
291 if (isset($_GET['nearlat']) && isset($_GET['nearlon']))
293 $sNearPointSQL = "ST_SetSRID(ST_Point(".(float)$_GET['nearlon'].",".(float)$_GET['nearlat']."),4326)";
294 $aSearches[0]['fLat'] = (float)$_GET['nearlat'];
295 $aSearches[0]['fLon'] = (float)$_GET['nearlon'];
296 $aSearches[0]['fRadius'] = 0.1;
299 $bSpecialTerms = false;
300 preg_match_all('/\\[(.*)=(.*)\\]/', $sQuery, $aSpecialTermsRaw, PREG_SET_ORDER);
301 $aSpecialTerms = array();
302 foreach($aSpecialTermsRaw as $aSpecialTerm)
304 $sQuery = str_replace($aSpecialTerm[0], ' ', $sQuery);
305 $aSpecialTerms[strtolower($aSpecialTerm[1])] = $aSpecialTerm[2];
308 preg_match_all('/\\[([\\w ]*)\\]/u', $sQuery, $aSpecialTermsRaw, PREG_SET_ORDER);
309 $aSpecialTerms = array();
310 if (isset($aStructuredQuery['amenity']) && $aStructuredQuery['amenity'])
312 $aSpecialTermsRaw[] = array('['.$aStructuredQuery['amenity'].']', $aStructuredQuery['amenity']);
313 unset($aStructuredQuery['amenity']);
315 foreach($aSpecialTermsRaw as $aSpecialTerm)
317 $sQuery = str_replace($aSpecialTerm[0], ' ', $sQuery);
318 $sToken = $oDB->getOne("select make_standard_name('".$aSpecialTerm[1]."') as string");
319 $sSQL = 'select * from (select word_id,word_token, word, class, type, country_code, operator';
320 $sSQL .= ' from word where word_token in (\' '.$sToken.'\')) as x where (class is not null and class not in (\'place\')) or country_code is not null';
321 if (CONST_Debug) var_Dump($sSQL);
322 $aSearchWords = $oDB->getAll($sSQL);
323 $aNewSearches = array();
324 foreach($aSearches as $aSearch)
326 foreach($aSearchWords as $aSearchTerm)
328 $aNewSearch = $aSearch;
329 if ($aSearchTerm['country_code'])
331 $aNewSearch['sCountryCode'] = strtolower($aSearchTerm['country_code']);
332 $aNewSearches[] = $aNewSearch;
333 $bSpecialTerms = true;
335 if ($aSearchTerm['class'])
337 $aNewSearch['sClass'] = $aSearchTerm['class'];
338 $aNewSearch['sType'] = $aSearchTerm['type'];
339 $aNewSearches[] = $aNewSearch;
340 $bSpecialTerms = true;
344 $aSearches = $aNewSearches;
347 // Split query into phrases
348 // Commas are used to reduce the search space by indicating where phrases split
349 if (sizeof($aStructuredQuery) > 0)
351 $aPhrases = $aStructuredQuery;
352 $bStructuredPhrases = true;
356 $aPhrases = explode(',',$sQuery);
357 $bStructuredPhrases = false;
360 // Convert each phrase to standard form
361 // Create a list of standard words
362 // Get all 'sets' of words
363 // Generate a complete list of all
365 foreach($aPhrases as $iPhrase => $sPhrase)
367 $aPhrase = $oDB->getRow("select make_standard_name('".pg_escape_string($sPhrase)."') as string");
368 if (PEAR::isError($aPhrase))
370 userError("Illegal query string (not an UTF-8 string): ".$sPhrase);
371 if (CONST_Debug) var_dump($aPhrase);
374 if (trim($aPhrase['string']))
376 $aPhrases[$iPhrase] = $aPhrase;
377 $aPhrases[$iPhrase]['words'] = explode(' ',$aPhrases[$iPhrase]['string']);
378 $aPhrases[$iPhrase]['wordsets'] = getWordSets($aPhrases[$iPhrase]['words'], 0);
379 $aTokens = array_merge($aTokens, getTokensFromSets($aPhrases[$iPhrase]['wordsets']));
383 unset($aPhrases[$iPhrase]);
387 // reindex phrases - we make assumptions later on
388 $aPhraseTypes = array_keys($aPhrases);
389 $aPhrases = array_values($aPhrases);
391 if (sizeof($aTokens))
394 // Check which tokens we have, get the ID numbers
395 $sSQL = 'select word_id,word_token, word, class, type, country_code, operator, search_name_count';
396 $sSQL .= ' from word where word_token in ('.join(',',array_map("getDBQuoted",$aTokens)).')';
397 //$sSQL .= ' and search_name_count < '.CONST_Max_Word_Frequency;
398 //$sSQL .= ' group by word_token, word, class, type, country_code';
400 if (CONST_Debug) var_Dump($sSQL);
402 $aValidTokens = array();
403 if (sizeof($aTokens)) $aDatabaseWords = $oDB->getAll($sSQL);
404 else $aDatabaseWords = array();
405 if (PEAR::IsError($aDatabaseWords))
407 failInternalError("Could not get word tokens.", $sSQL, $aDatabaseWords);
409 $aPossibleMainWordIDs = array();
410 $aWordFrequencyScores = array();
411 foreach($aDatabaseWords as $aToken)
413 // Very special case - require 2 letter country param to match the country code found
414 if ($bStructuredPhrases && $aToken['country_code'] && !empty($aStructuredQuery['country'])
415 && strlen($aStructuredQuery['country']) == 2 && strtolower($aStructuredQuery['country']) != $aToken['country_code'])
420 if (isset($aValidTokens[$aToken['word_token']]))
422 $aValidTokens[$aToken['word_token']][] = $aToken;
426 $aValidTokens[$aToken['word_token']] = array($aToken);
428 if (!$aToken['class'] && !$aToken['country_code']) $aPossibleMainWordIDs[$aToken['word_id']] = 1;
429 $aWordFrequencyScores[$aToken['word_id']] = $aToken['search_name_count'] + 1;
431 if (CONST_Debug) var_Dump($aPhrases, $aValidTokens);
433 // Try and calculate GB postcodes we might be missing
434 foreach($aTokens as $sToken)
436 // Source of gb postcodes is now definitive - always use
437 if (preg_match('/^([A-Z][A-Z]?[0-9][0-9A-Z]? ?[0-9])([A-Z][A-Z])$/', strtoupper(trim($sToken)), $aData))
439 if (substr($aData[1],-2,1) != ' ')
441 $aData[0] = substr($aData[0],0,strlen($aData[1]-1)).' '.substr($aData[0],strlen($aData[1]-1));
442 $aData[1] = substr($aData[1],0,-1).' '.substr($aData[1],-1,1);
444 $aGBPostcodeLocation = gbPostcodeCalculate($aData[0], $aData[1], $aData[2], $oDB);
445 if ($aGBPostcodeLocation)
447 $aValidTokens[$sToken] = $aGBPostcodeLocation;
452 foreach($aTokens as $sToken)
454 // Unknown single word token with a number - assume it is a house number
455 if (!isset($aValidTokens[' '.$sToken]) && strpos($sToken,' ') === false && preg_match('/[0-9]/', $sToken))
457 $aValidTokens[' '.$sToken] = array(array('class'=>'place','type'=>'house'));
461 // Any words that have failed completely?
464 // Start the search process
465 $aResultPlaceIDs = array();
468 Calculate all searches using aValidTokens i.e.
470 'Wodsworth Road, Sheffield' =>
474 0 1 (wodsworth)(road)
477 Score how good the search is so they can be ordered
479 foreach($aPhrases as $iPhrase => $sPhrase)
481 $aNewPhraseSearches = array();
482 if ($bStructuredPhrases) $sPhraseType = $aPhraseTypes[$iPhrase];
483 else $sPhraseType = '';
485 foreach($aPhrases[$iPhrase]['wordsets'] as $aWordset)
487 $aWordsetSearches = $aSearches;
489 // Add all words from this wordset
490 foreach($aWordset as $iToken => $sToken)
492 //echo "<br><b>$sToken</b>";
493 $aNewWordsetSearches = array();
495 foreach($aWordsetSearches as $aCurrentSearch)
498 //var_dump($aCurrentSearch);
501 // If the token is valid
502 if (isset($aValidTokens[' '.$sToken]))
504 foreach($aValidTokens[' '.$sToken] as $aSearchTerm)
506 $aSearch = $aCurrentSearch;
507 $aSearch['iSearchRank']++;
508 if (($sPhraseType == '' || $sPhraseType == 'country') && !empty($aSearchTerm['country_code']) && $aSearchTerm['country_code'] != '0')
510 if ($aSearch['sCountryCode'] === false)
512 $aSearch['sCountryCode'] = strtolower($aSearchTerm['country_code']);
513 // Country is almost always at the end of the string - increase score for finding it anywhere else (optimisation)
514 // If reverse order is enabled, it may appear at the beginning as well.
515 if (($iToken+1 != sizeof($aWordset) || $iPhrase+1 != sizeof($aPhrases)) &&
516 (!$bReverseInPlan || $iToken > 0 || $iPhrase > 0))
518 $aSearch['iSearchRank'] += 5;
520 if ($aSearch['iSearchRank'] < $iMaxRank) $aNewWordsetSearches[] = $aSearch;
523 elseif (isset($aSearchTerm['lat']) && $aSearchTerm['lat'] !== '' && $aSearchTerm['lat'] !== null)
525 if ($aSearch['fLat'] === '')
527 $aSearch['fLat'] = $aSearchTerm['lat'];
528 $aSearch['fLon'] = $aSearchTerm['lon'];
529 $aSearch['fRadius'] = $aSearchTerm['radius'];
530 if ($aSearch['iSearchRank'] < $iMaxRank) $aNewWordsetSearches[] = $aSearch;
533 elseif ($sPhraseType == 'postalcode')
535 // 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
536 if (sizeof($aSearch['aName']))
538 $aSearch['aAddress'] = array_merge($aSearch['aAddress'], $aSearch['aName']);
539 $aSearch['aName'] = array();
540 $aSearch['aName'][$aSearchTerm['word_id']] = $aSearchTerm['word_id'];
542 if ($aSearch['iSearchRank'] < $iMaxRank) $aNewWordsetSearches[] = $aSearch;
544 elseif (($sPhraseType == '' || $sPhraseType == 'street') && $aSearchTerm['class'] == 'place' && $aSearchTerm['type'] == 'house')
546 if ($aSearch['sHouseNumber'] === '')
548 $aSearch['sHouseNumber'] = $sToken;
549 if ($aSearch['iSearchRank'] < $iMaxRank) $aNewWordsetSearches[] = $aSearch;
551 // Fall back to not searching for this item (better than nothing)
552 $aSearch = $aCurrentSearch;
553 $aSearch['iSearchRank'] += 1;
554 if ($aSearch['iSearchRank'] < $iMaxRank) $aNewWordsetSearches[] = $aSearch;
558 elseif ($sPhraseType == '' && $aSearchTerm['class'] !== '' && $aSearchTerm['class'] !== null)
560 if ($aSearch['sClass'] === '')
562 $aSearch['sOperator'] = $aSearchTerm['operator'];
563 $aSearch['sClass'] = $aSearchTerm['class'];
564 $aSearch['sType'] = $aSearchTerm['type'];
565 if (sizeof($aSearch['aName'])) $aSearch['sOperator'] = 'name';
566 else $aSearch['sOperator'] = 'near'; // near = in for the moment
568 // Do we have a shortcut id?
569 if ($aSearch['sOperator'] == 'name')
571 $sSQL = "select get_tagpair('".$aSearch['sClass']."', '".$aSearch['sType']."')";
572 if ($iAmenityID = $oDB->getOne($sSQL))
574 $aValidTokens[$aSearch['sClass'].':'.$aSearch['sType']] = array('word_id' => $iAmenityID);
575 $aSearch['aName'][$iAmenityID] = $iAmenityID;
576 $aSearch['sClass'] = '';
577 $aSearch['sType'] = '';
580 if ($aSearch['iSearchRank'] < $iMaxRank) $aNewWordsetSearches[] = $aSearch;
583 elseif (isset($aSearchTerm['word_id']) && $aSearchTerm['word_id'])
585 if (sizeof($aSearch['aName']))
587 if ((!$bStructuredPhrases || $iPhrase > 0) && $sPhraseType != 'country' && (!isset($aValidTokens[$sToken]) || strlen($sToken) < 4 || strpos($sToken, ' ') !== false))
589 $aSearch['aAddress'][$aSearchTerm['word_id']] = $aSearchTerm['word_id'];
593 $aCurrentSearch['aFullNameAddress'][$aSearchTerm['word_id']] = $aSearchTerm['word_id'];
594 $aSearch['iSearchRank'] += 1000; // skip;
599 $aSearch['aName'][$aSearchTerm['word_id']] = $aSearchTerm['word_id'];
600 //$aSearch['iNamePhrase'] = $iPhrase;
602 if ($aSearch['iSearchRank'] < $iMaxRank) $aNewWordsetSearches[] = $aSearch;
606 if (isset($aValidTokens[$sToken]))
608 // Allow searching for a word - but at extra cost
609 foreach($aValidTokens[$sToken] as $aSearchTerm)
611 if (isset($aSearchTerm['word_id']) && $aSearchTerm['word_id'])
613 if ((!$bStructuredPhrases || $iPhrase > 0) && sizeof($aCurrentSearch['aName']) && strlen($sToken) >= 4)
615 $aSearch = $aCurrentSearch;
616 $aSearch['iSearchRank'] += 1;
617 if ($aWordFrequencyScores[$aSearchTerm['word_id']] < CONST_Max_Word_Frequency)
619 $aSearch['aAddress'][$aSearchTerm['word_id']] = $aSearchTerm['word_id'];
620 if ($aSearch['iSearchRank'] < $iMaxRank) $aNewWordsetSearches[] = $aSearch;
622 elseif (isset($aValidTokens[' '.$sToken])) // revert to the token version?
624 foreach($aValidTokens[' '.$sToken] as $aSearchTermToken)
626 if (empty($aSearchTermToken['country_code'])
627 && empty($aSearchTermToken['lat'])
628 && empty($aSearchTermToken['class']))
630 $aSearch = $aCurrentSearch;
631 $aSearch['iSearchRank'] += 1;
632 $aSearch['aAddress'][$aSearchTermToken['word_id']] = $aSearchTermToken['word_id'];
633 if ($aSearch['iSearchRank'] < $iMaxRank) $aNewWordsetSearches[] = $aSearch;
639 $aSearch['aAddressNonSearch'][$aSearchTerm['word_id']] = $aSearchTerm['word_id'];
640 if ($aSearch['iSearchRank'] < $iMaxRank) $aNewWordsetSearches[] = $aSearch;
644 if (!sizeof($aCurrentSearch['aName']) || $aCurrentSearch['iNamePhrase'] == $iPhrase)
646 $aSearch = $aCurrentSearch;
647 $aSearch['iSearchRank'] += 2;
648 if (preg_match('#^[0-9]+$#', $sToken)) $aSearch['iSearchRank'] += 2;
649 if ($aWordFrequencyScores[$aSearchTerm['word_id']] < CONST_Max_Word_Frequency)
650 $aSearch['aName'][$aSearchTerm['word_id']] = $aSearchTerm['word_id'];
652 $aSearch['aNameNonSearch'][$aSearchTerm['word_id']] = $aSearchTerm['word_id'];
653 $aSearch['iNamePhrase'] = $iPhrase;
654 if ($aSearch['iSearchRank'] < $iMaxRank) $aNewWordsetSearches[] = $aSearch;
661 // Allow skipping a word - but at EXTREAM cost
662 //$aSearch = $aCurrentSearch;
663 //$aSearch['iSearchRank']+=100;
664 //$aNewWordsetSearches[] = $aSearch;
668 usort($aNewWordsetSearches, 'bySearchRank');
669 $aWordsetSearches = array_slice($aNewWordsetSearches, 0, 50);
671 //var_Dump('<hr>',sizeof($aWordsetSearches)); exit;
673 $aNewPhraseSearches = array_merge($aNewPhraseSearches, $aNewWordsetSearches);
674 usort($aNewPhraseSearches, 'bySearchRank');
676 $aSearchHash = array();
677 foreach($aNewPhraseSearches as $iSearch => $aSearch)
679 $sHash = serialize($aSearch);
680 if (isset($aSearchHash[$sHash])) unset($aNewPhraseSearches[$iSearch]);
681 else $aSearchHash[$sHash] = 1;
684 $aNewPhraseSearches = array_slice($aNewPhraseSearches, 0, 50);
687 // Re-group the searches by their score, junk anything over 20 as just not worth trying
688 $aGroupedSearches = array();
689 foreach($aNewPhraseSearches as $aSearch)
691 if ($aSearch['iSearchRank'] < $iMaxRank)
693 if (!isset($aGroupedSearches[$aSearch['iSearchRank']])) $aGroupedSearches[$aSearch['iSearchRank']] = array();
694 $aGroupedSearches[$aSearch['iSearchRank']][] = $aSearch;
697 ksort($aGroupedSearches);
700 $aSearches = array();
701 foreach($aGroupedSearches as $iScore => $aNewSearches)
703 $iSearchCount += sizeof($aNewSearches);
704 $aSearches = array_merge($aSearches, $aNewSearches);
705 if ($iSearchCount > 50) break;
708 //if (CONST_Debug) _debugDumpGroupedSearches($aGroupedSearches, $aValidTokens);
714 // Re-group the searches by their score, junk anything over 20 as just not worth trying
715 $aGroupedSearches = array();
716 foreach($aSearches as $aSearch)
718 if ($aSearch['iSearchRank'] < $iMaxRank)
720 if (!isset($aGroupedSearches[$aSearch['iSearchRank']])) $aGroupedSearches[$aSearch['iSearchRank']] = array();
721 $aGroupedSearches[$aSearch['iSearchRank']][] = $aSearch;
724 ksort($aGroupedSearches);
727 if (CONST_Debug) var_Dump($aGroupedSearches);
731 $aCopyGroupedSearches = $aGroupedSearches;
732 foreach($aCopyGroupedSearches as $iGroup => $aSearches)
734 foreach($aSearches as $iSearch => $aSearch)
736 if (sizeof($aSearch['aAddress']))
738 $iReverseItem = array_pop($aSearch['aAddress']);
739 if (isset($aPossibleMainWordIDs[$iReverseItem]))
741 $aSearch['aAddress'] = array_merge($aSearch['aAddress'], $aSearch['aName']);
742 $aSearch['aName'] = array($iReverseItem);
743 $aGroupedSearches[$iGroup][] = $aSearch;
745 //$aReverseSearch['aName'][$iReverseItem] = $iReverseItem;
746 //$aGroupedSearches[$iGroup][] = $aReverseSearch;
752 if (CONST_Search_TryDroppedAddressTerms && sizeof($aStructuredQuery) > 0)
754 $aCopyGroupedSearches = $aGroupedSearches;
755 foreach($aCopyGroupedSearches as $iGroup => $aSearches)
757 foreach($aSearches as $iSearch => $aSearch)
759 $aReductionsList = array($aSearch['aAddress']);
760 $iSearchRank = $aSearch['iSearchRank'];
761 while(sizeof($aReductionsList) > 0)
764 if ($iSearchRank > iMaxRank) break 3;
765 $aNewReductionsList = array();
766 foreach($aReductionsList as $aReductionsWordList)
768 for ($iReductionWord = 0; $iReductionWord < sizeof($aReductionsWordList); $iReductionWord++)
770 $aReductionsWordListResult = array_merge(array_slice($aReductionsWordList, 0, $iReductionWord), array_slice($aReductionsWordList, $iReductionWord+1));
771 $aReverseSearch = $aSearch;
772 $aSearch['aAddress'] = $aReductionsWordListResult;
773 $aSearch['iSearchRank'] = $iSearchRank;
774 $aGroupedSearches[$iSearchRank][] = $aReverseSearch;
775 if (sizeof($aReductionsWordListResult) > 0)
777 $aNewReductionsList[] = $aReductionsWordListResult;
781 $aReductionsList = $aNewReductionsList;
785 ksort($aGroupedSearches);
788 // Filter out duplicate searches
789 $aSearchHash = array();
790 foreach($aGroupedSearches as $iGroup => $aSearches)
792 foreach($aSearches as $iSearch => $aSearch)
794 $sHash = serialize($aSearch);
795 if (isset($aSearchHash[$sHash]))
797 unset($aGroupedSearches[$iGroup][$iSearch]);
798 if (sizeof($aGroupedSearches[$iGroup]) == 0) unset($aGroupedSearches[$iGroup]);
802 $aSearchHash[$sHash] = 1;
807 if (CONST_Debug) _debugDumpGroupedSearches($aGroupedSearches, $aValidTokens);
811 foreach($aGroupedSearches as $iGroupedRank => $aSearches)
814 foreach($aSearches as $aSearch)
818 if (CONST_Debug) { echo "<hr><b>Search Loop, group $iGroupLoop, loop $iQueryLoop</b>"; }
819 if (CONST_Debug) _debugDumpGroupedSearches(array($iGroupedRank => array($aSearch)), $aValidTokens);
822 // Must have a location term
823 if (!sizeof($aSearch['aName']) && !sizeof($aSearch['aAddress']) && !$aSearch['fLon'])
825 if ($aSearch['sCountryCode'] && !$aSearch['sClass'] && !$aSearch['sHouseNumber'])
827 if (4 >= $iMinAddressRank && 4 <= $iMaxAddressRank)
829 $sSQL = "select place_id from placex where calculated_country_code='".$aSearch['sCountryCode']."' and rank_search = 4";
830 if ($sCountryCodesSQL) $sSQL .= " and calculated_country_code in ($sCountryCodesSQL)";
831 $sSQL .= " order by st_area(geometry) desc limit 1";
832 if (CONST_Debug) var_dump($sSQL);
833 $aPlaceIDs = $oDB->getCol($sSQL);
838 if (!$bBoundingBoxSearch && !$aSearch['fLon']) continue;
839 if (!$aSearch['sClass']) continue;
840 $sSQL = "select count(*) from pg_tables where tablename = 'place_classtype_".$aSearch['sClass']."_".$aSearch['sType']."'";
841 if ($oDB->getOne($sSQL))
843 $sSQL = "select place_id from place_classtype_".$aSearch['sClass']."_".$aSearch['sType']." ct";
844 if ($sCountryCodesSQL) $sSQL .= " join placex using (place_id)";
845 $sSQL .= " where st_contains($sViewboxSmallSQL, ct.centroid)";
846 if ($sCountryCodesSQL) $sSQL .= " and calculated_country_code in ($sCountryCodesSQL)";
847 if (sizeof($aExcludePlaceIDs))
849 $sSQL .= " and place_id not in (".join(',',$aExcludePlaceIDs).")";
851 if ($sViewboxCentreSQL) $sSQL .= " order by st_distance($sViewboxCentreSQL, ct.centroid) asc";
852 $sSQL .= " limit $iLimit";
853 if (CONST_Debug) var_dump($sSQL);
854 $aPlaceIDs = $oDB->getCol($sSQL);
856 // If excluded place IDs are given, it is fair to assume that
857 // there have been results in the small box, so no further
858 // expansion in that case.
859 if (!sizeof($aPlaceIDs) && !sizeof($aExcludePlaceIDs))
861 $sSQL = "select place_id from place_classtype_".$aSearch['sClass']."_".$aSearch['sType']." ct";
862 if ($sCountryCodesSQL) $sSQL .= " join placex using (place_id)";
863 $sSQL .= " where st_contains($sViewboxLargeSQL, ct.centroid)";
864 if ($sCountryCodesSQL) $sSQL .= " and calculated_country_code in ($sCountryCodesSQL)";
865 if ($sViewboxCentreSQL) $sSQL .= " order by st_distance($sViewboxCentreSQL, ct.centroid) asc";
866 $sSQL .= " limit $iLimit";
867 if (CONST_Debug) var_dump($sSQL);
868 $aPlaceIDs = $oDB->getCol($sSQL);
873 $sSQL = "select place_id from placex where class='".$aSearch['sClass']."' and type='".$aSearch['sType']."'";
874 $sSQL .= " and st_contains($sViewboxSmallSQL, geometry) and linked_place_id is null";
875 if ($sCountryCodesSQL) $sSQL .= " and calculated_country_code in ($sCountryCodesSQL)";
876 if ($sViewboxCentreSQL) $sSQL .= " order by st_distance($sViewboxCentreSQL, centroid) asc";
877 $sSQL .= " limit $iLimit";
878 if (CONST_Debug) var_dump($sSQL);
879 $aPlaceIDs = $oDB->getCol($sSQL);
885 $aPlaceIDs = array();
887 // First we need a position, either aName or fLat or both
891 // TODO: filter out the pointless search terms (2 letter name tokens and less)
892 // they might be right - but they are just too darned expensive to run
893 if (sizeof($aSearch['aName'])) $aTerms[] = "name_vector @> ARRAY[".join($aSearch['aName'],",")."]";
894 if (sizeof($aSearch['aNameNonSearch'])) $aTerms[] = "array_cat(name_vector,ARRAY[]::integer[]) @> ARRAY[".join($aSearch['aNameNonSearch'],",")."]";
895 if (sizeof($aSearch['aAddress']) && $aSearch['aName'] != $aSearch['aAddress'])
897 // For infrequent name terms disable index usage for address
898 if (CONST_Search_NameOnlySearchFrequencyThreshold &&
899 sizeof($aSearch['aName']) == 1 &&
900 $aWordFrequencyScores[$aSearch['aName'][reset($aSearch['aName'])]] < CONST_Search_NameOnlySearchFrequencyThreshold)
902 $aTerms[] = "array_cat(nameaddress_vector,ARRAY[]::integer[]) @> ARRAY[".join(array_merge($aSearch['aAddress'],$aSearch['aAddressNonSearch']),",")."]";
906 $aTerms[] = "nameaddress_vector @> ARRAY[".join($aSearch['aAddress'],",")."]";
907 if (sizeof($aSearch['aAddressNonSearch'])) $aTerms[] = "array_cat(nameaddress_vector,ARRAY[]::integer[]) @> ARRAY[".join($aSearch['aAddressNonSearch'],",")."]";
910 if ($aSearch['sCountryCode']) $aTerms[] = "country_code = '".pg_escape_string($aSearch['sCountryCode'])."'";
911 if ($aSearch['sHouseNumber']) $aTerms[] = "address_rank between 16 and 27";
912 if ($aSearch['fLon'] && $aSearch['fLat'])
914 $aTerms[] = "ST_DWithin(centroid, ST_SetSRID(ST_Point(".$aSearch['fLon'].",".$aSearch['fLat']."),4326), ".$aSearch['fRadius'].")";
915 $aOrder[] = "ST_Distance(centroid, ST_SetSRID(ST_Point(".$aSearch['fLon'].",".$aSearch['fLat']."),4326)) ASC";
917 if (sizeof($aExcludePlaceIDs))
919 $aTerms[] = "place_id not in (".join(',',$aExcludePlaceIDs).")";
921 if ($sCountryCodesSQL)
923 $aTerms[] = "country_code in ($sCountryCodesSQL)";
926 if ($bBoundingBoxSearch) $aTerms[] = "centroid && $sViewboxSmallSQL";
927 if ($sNearPointSQL) $aOrder[] = "ST_Distance($sNearPointSQL, centroid) asc";
929 $sImportanceSQL = '(case when importance = 0 OR importance IS NULL then 0.75-(search_rank::float/40) else importance end)';
930 if ($sViewboxSmallSQL) $sImportanceSQL .= " * case when ST_Contains($sViewboxSmallSQL, centroid) THEN 1 ELSE 0.5 END";
931 if ($sViewboxLargeSQL) $sImportanceSQL .= " * case when ST_Contains($sViewboxLargeSQL, centroid) THEN 1 ELSE 0.5 END";
932 $aOrder[] = "$sImportanceSQL DESC";
933 if (sizeof($aSearch['aFullNameAddress']))
935 $aOrder[] = '(select count(*) from (select unnest(ARRAY['.join($aSearch['aFullNameAddress'],",").']) INTERSECT select unnest(nameaddress_vector))s) DESC';
940 $sSQL = "select place_id";
941 $sSQL .= " from search_name";
942 $sSQL .= " where ".join(' and ',$aTerms);
943 $sSQL .= " order by ".join(', ',$aOrder);
944 if ($aSearch['sHouseNumber'] || $aSearch['sClass'])
945 $sSQL .= " limit 50";
946 elseif (!sizeof($aSearch['aName']) && !sizeof($aSearch['aAddress']) && $aSearch['sClass'])
949 $sSQL .= " limit ".$iLimit;
951 if (CONST_Debug) { var_dump($sSQL); }
952 $aViewBoxPlaceIDs = $oDB->getAll($sSQL);
953 if (PEAR::IsError($aViewBoxPlaceIDs))
955 failInternalError("Could not get places for search terms.", $sSQL, $aViewBoxPlaceIDs);
957 //var_dump($aViewBoxPlaceIDs);
958 // Did we have an viewbox matches?
959 $aPlaceIDs = array();
960 $bViewBoxMatch = false;
961 foreach($aViewBoxPlaceIDs as $aViewBoxRow)
963 //if ($bViewBoxMatch == 1 && $aViewBoxRow['in_small'] == 'f') break;
964 //if ($bViewBoxMatch == 2 && $aViewBoxRow['in_large'] == 'f') break;
965 //if ($aViewBoxRow['in_small'] == 't') $bViewBoxMatch = 1;
966 //else if ($aViewBoxRow['in_large'] == 't') $bViewBoxMatch = 2;
967 $aPlaceIDs[] = $aViewBoxRow['place_id'];
970 //var_Dump($aPlaceIDs);
973 if ($aSearch['sHouseNumber'] && sizeof($aPlaceIDs))
975 $aRoadPlaceIDs = $aPlaceIDs;
976 $sPlaceIDs = join(',',$aPlaceIDs);
978 // Now they are indexed look for a house attached to a street we found
979 $sHouseNumberRegex = '\\\\m'.str_replace(' ','[-,/ ]',$aSearch['sHouseNumber']).'\\\\M';
980 $sSQL = "select place_id from placex where parent_place_id in (".$sPlaceIDs.") and housenumber ~* E'".$sHouseNumberRegex."'";
981 if (sizeof($aExcludePlaceIDs))
983 $sSQL .= " and place_id not in (".join(',',$aExcludePlaceIDs).")";
985 $sSQL .= " limit $iLimit";
986 if (CONST_Debug) var_dump($sSQL);
987 $aPlaceIDs = $oDB->getCol($sSQL);
989 // If not try the aux fallback table
990 if (!sizeof($aPlaceIDs))
992 $sSQL = "select place_id from location_property_aux where parent_place_id in (".$sPlaceIDs.") and housenumber = '".pg_escape_string($aSearch['sHouseNumber'])."'";
993 if (sizeof($aExcludePlaceIDs))
995 $sSQL .= " and place_id not in (".join(',',$aExcludePlaceIDs).")";
997 //$sSQL .= " limit $iLimit";
998 if (CONST_Debug) var_dump($sSQL);
999 $aPlaceIDs = $oDB->getCol($sSQL);
1002 if (!sizeof($aPlaceIDs))
1004 $sSQL = "select place_id from location_property_tiger where parent_place_id in (".$sPlaceIDs.") and housenumber = '".pg_escape_string($aSearch['sHouseNumber'])."'";
1005 if (sizeof($aExcludePlaceIDs))
1007 $sSQL .= " and place_id not in (".join(',',$aExcludePlaceIDs).")";
1009 //$sSQL .= " limit $iLimit";
1010 if (CONST_Debug) var_dump($sSQL);
1011 $aPlaceIDs = $oDB->getCol($sSQL);
1014 // Fallback to the road
1015 if (!sizeof($aPlaceIDs) && preg_match('/[0-9]+/', $aSearch['sHouseNumber']))
1017 $aPlaceIDs = $aRoadPlaceIDs;
1022 if ($aSearch['sClass'] && sizeof($aPlaceIDs))
1024 $sPlaceIDs = join(',',$aPlaceIDs);
1025 $aClassPlaceIDs = array();
1027 if (!$aSearch['sOperator'] || $aSearch['sOperator'] == 'name')
1029 // If they were searching for a named class (i.e. 'Kings Head pub') then we might have an extra match
1030 $sSQL = "select place_id from placex where place_id in ($sPlaceIDs) and class='".$aSearch['sClass']."' and type='".$aSearch['sType']."'";
1031 $sSQL .= " and linked_place_id is null";
1032 if ($sCountryCodesSQL) $sSQL .= " and calculated_country_code in ($sCountryCodesSQL)";
1033 $sSQL .= " order by rank_search asc limit $iLimit";
1034 if (CONST_Debug) var_dump($sSQL);
1035 $aClassPlaceIDs = $oDB->getCol($sSQL);
1038 if (!$aSearch['sOperator'] || $aSearch['sOperator'] == 'near') // & in
1040 $sSQL = "select count(*) from pg_tables where tablename = 'place_classtype_".$aSearch['sClass']."_".$aSearch['sType']."'";
1041 $bCacheTable = $oDB->getOne($sSQL);
1043 $sSQL = "select min(rank_search) from placex where place_id in ($sPlaceIDs)";
1045 if (CONST_Debug) var_dump($sSQL);
1046 $iMaxRank = ((int)$oDB->getOne($sSQL));
1048 // For state / country level searches the normal radius search doesn't work very well
1049 $sPlaceGeom = false;
1050 if ($iMaxRank < 9 && $bCacheTable)
1052 // Try and get a polygon to search in instead
1053 $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";
1054 if (CONST_Debug) var_dump($sSQL);
1055 $sPlaceGeom = $oDB->getOne($sSQL);
1065 $sSQL = "select place_id from placex where place_id in ($sPlaceIDs) and rank_search < $iMaxRank";
1066 if (CONST_Debug) var_dump($sSQL);
1067 $aPlaceIDs = $oDB->getCol($sSQL);
1068 $sPlaceIDs = join(',',$aPlaceIDs);
1071 if ($sPlaceIDs || $sPlaceGeom)
1077 // More efficient - can make the range bigger
1081 if ($sNearPointSQL) $sOrderBySQL = "ST_Distance($sNearPointSQL, l.centroid)";
1082 else if ($sPlaceIDs) $sOrderBySQL = "ST_Distance(l.centroid, f.geometry)";
1083 else if ($sPlaceGeom) $sOrderBysSQL = "ST_Distance(st_centroid('".$sPlaceGeom."'), l.centroid)";
1085 $sSQL = "select distinct l.place_id".($sOrderBySQL?','.$sOrderBySQL:'')." from place_classtype_".$aSearch['sClass']."_".$aSearch['sType']." as l";
1086 if ($sCountryCodesSQL) $sSQL .= " join placex as lp using (place_id)";
1089 $sSQL .= ",placex as f where ";
1090 $sSQL .= "f.place_id in ($sPlaceIDs) and ST_DWithin(l.centroid, f.centroid, $fRange) ";
1095 $sSQL .= "ST_Contains('".$sPlaceGeom."', l.centroid) ";
1097 if (sizeof($aExcludePlaceIDs))
1099 $sSQL .= " and l.place_id not in (".join(',',$aExcludePlaceIDs).")";
1101 if ($sCountryCodesSQL) $sSQL .= " and lp.calculated_country_code in ($sCountryCodesSQL)";
1102 if ($sOrderBySQL) $sSQL .= "order by ".$sOrderBySQL." asc";
1103 if ($iOffset) $sSQL .= " offset $iOffset";
1104 $sSQL .= " limit $iLimit";
1105 if (CONST_Debug) var_dump($sSQL);
1106 $aClassPlaceIDs = array_merge($aClassPlaceIDs, $oDB->getCol($sSQL));
1110 if (isset($aSearch['fRadius']) && $aSearch['fRadius']) $fRange = $aSearch['fRadius'];
1113 if ($sNearPointSQL) $sOrderBySQL = "ST_Distance($sNearPointSQL, l.geometry)";
1114 else $sOrderBySQL = "ST_Distance(l.geometry, f.geometry)";
1116 $sSQL = "select distinct l.place_id".($sOrderBysSQL?','.$sOrderBysSQL:'')." from placex as l,placex as f where ";
1117 $sSQL .= "f.place_id in ( $sPlaceIDs) and ST_DWithin(l.geometry, f.centroid, $fRange) ";
1118 $sSQL .= "and l.class='".$aSearch['sClass']."' and l.type='".$aSearch['sType']."' ";
1119 if (sizeof($aExcludePlaceIDs))
1121 $sSQL .= " and l.place_id not in (".join(',',$aExcludePlaceIDs).")";
1123 if ($sCountryCodesSQL) $sSQL .= " and l.calculated_country_code in ($sCountryCodesSQL)";
1124 if ($sOrderBy) $sSQL .= "order by ".$OrderBysSQL." asc";
1125 if ($iOffset) $sSQL .= " offset $iOffset";
1126 $sSQL .= " limit $iLimit";
1127 if (CONST_Debug) var_dump($sSQL);
1128 $aClassPlaceIDs = array_merge($aClassPlaceIDs, $oDB->getCol($sSQL));
1133 $aPlaceIDs = $aClassPlaceIDs;
1139 if (PEAR::IsError($aPlaceIDs))
1141 failInternalError("Could not get place IDs from tokens." ,$sSQL, $aPlaceIDs);
1144 if (CONST_Debug) { echo "<br><b>Place IDs:</b> "; var_Dump($aPlaceIDs); }
1146 foreach($aPlaceIDs as $iPlaceID)
1148 $aResultPlaceIDs[$iPlaceID] = $iPlaceID;
1150 if ($iQueryLoop > 20) break;
1153 if (isset($aResultPlaceIDs) && sizeof($aResultPlaceIDs) && ($iMinAddressRank != 0 || $iMaxAddressRank != 30))
1155 // Need to verify passes rank limits before dropping out of the loop (yuk!)
1156 $sSQL = "select place_id from placex where place_id in (".join(',',$aResultPlaceIDs).") ";
1157 $sSQL .= "and (placex.rank_address between $iMinAddressRank and $iMaxAddressRank ";
1158 if (14 >= $iMinAddressRank && 14 <= $iMaxAddressRank) $sSQL .= " OR (extratags->'place') = 'city'";
1159 if ($aAddressRankList) $sSQL .= " OR placex.rank_address in (".join(',',$aAddressRankList).")";
1161 if (CONST_Debug) var_dump($sSQL);
1162 $aResultPlaceIDs = $oDB->getCol($sSQL);
1167 if (isset($aResultPlaceIDs) && sizeof($aResultPlaceIDs)) break;
1168 if ($iGroupLoop > 4) break;
1169 if ($iQueryLoop > 30) break;
1172 // Did we find anything?
1173 if (isset($aResultPlaceIDs) && sizeof($aResultPlaceIDs))
1175 //var_Dump($aResultPlaceIDs);exit;
1176 // Get the details for display (is this a redundant extra step?)
1177 $sPlaceIDs = join(',',$aResultPlaceIDs);
1178 $sOrderSQL = 'CASE ';
1179 foreach(array_keys($aResultPlaceIDs) as $iOrder => $iPlaceID)
1181 $sOrderSQL .= 'when min(place_id) = '.$iPlaceID.' then '.$iOrder.' ';
1183 $sOrderSQL .= ' ELSE 10000000 END';
1184 $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,";
1185 $sSQL .= "get_address_by_language(place_id, $sLanguagePrefArraySQL) as langaddress,";
1186 $sSQL .= "get_name_by_language(name, $sLanguagePrefArraySQL) as placename,";
1187 $sSQL .= "get_name_by_language(name, ARRAY['ref']) as ref,";
1188 $sSQL .= "avg(ST_X(centroid)) as lon,avg(ST_Y(centroid)) as lat, ";
1189 //$sSQL .= $sOrderSQL." as porder, ";
1190 $sSQL .= "coalesce(importance,0.75-(rank_search::float/40)) as importance, ";
1191 $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, ";
1192 $sSQL .= "(extratags->'place') as extra_place ";
1193 $sSQL .= "from placex where place_id in ($sPlaceIDs) ";
1194 $sSQL .= "and (placex.rank_address between $iMinAddressRank and $iMaxAddressRank ";
1195 if (14 >= $iMinAddressRank && 14 <= $iMaxAddressRank) $sSQL .= " OR (extratags->'place') = 'city'";
1196 if ($aAddressRankList) $sSQL .= " OR placex.rank_address in (".join(',',$aAddressRankList).")";
1198 if ($sAllowedTypesSQLList) $sSQL .= "and placex.class in $sAllowedTypesSQLList ";
1199 $sSQL .= "and linked_place_id is null ";
1200 $sSQL .= "group by osm_type,osm_id,class,type,admin_level,rank_search,rank_address,calculated_country_code,importance";
1201 if (!$bDeDupe) $sSQL .= ",place_id";
1202 $sSQL .= ",langaddress ";
1203 $sSQL .= ",placename ";
1205 $sSQL .= ",extratags->'place' ";
1207 $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,";
1208 $sSQL .= "get_address_by_language(place_id, $sLanguagePrefArraySQL) as langaddress,";
1209 $sSQL .= "null as placename,";
1210 $sSQL .= "null as ref,";
1211 $sSQL .= "avg(ST_X(centroid)) as lon,avg(ST_Y(centroid)) as lat, ";
1212 //$sSQL .= $sOrderSQL." as porder, ";
1213 $sSQL .= "-0.15 as importance, ";
1214 $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, ";
1215 $sSQL .= "null as extra_place ";
1216 $sSQL .= "from location_property_tiger where place_id in ($sPlaceIDs) ";
1217 $sSQL .= "and 30 between $iMinAddressRank and $iMaxAddressRank ";
1218 $sSQL .= "group by place_id";
1219 if (!$bDeDupe) $sSQL .= ",place_id";
1221 $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,";
1222 $sSQL .= "get_address_by_language(place_id, $sLanguagePrefArraySQL) as langaddress,";
1223 $sSQL .= "null as placename,";
1224 $sSQL .= "null as ref,";
1225 $sSQL .= "avg(ST_X(centroid)) as lon,avg(ST_Y(centroid)) as lat, ";
1226 //$sSQL .= $sOrderSQL." as porder, ";
1227 $sSQL .= "-0.10 as importance, ";
1228 $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, ";
1229 $sSQL .= "null as extra_place ";
1230 $sSQL .= "from location_property_aux where place_id in ($sPlaceIDs) ";
1231 $sSQL .= "and 30 between $iMinAddressRank and $iMaxAddressRank ";
1232 $sSQL .= "group by place_id";
1233 if (!$bDeDupe) $sSQL .= ",place_id";
1234 $sSQL .= ",get_address_by_language(place_id, $sLanguagePrefArraySQL) ";
1235 $sSQL .= "order by importance desc";
1236 //$sSQL .= "order by rank_search,rank_address,porder asc";
1237 if (CONST_Debug) { echo "<hr>"; var_dump($sSQL); }
1238 $aSearchResults = $oDB->getAll($sSQL);
1239 //var_dump($sSQL,$aSearchResults);exit;
1241 if (PEAR::IsError($aSearchResults))
1243 failInternalError("Could not get details for place.", $sSQL, $aSearchResults);
1246 } // end if ($sQuery)
1249 if (isset($_GET['nearlat']) && trim($_GET['nearlat'])!=='' && isset($_GET['nearlon']) && trim($_GET['nearlon']) !== '')
1251 $iPlaceID = geocodeReverse((float)$_GET['nearlat'], (float)$_GET['nearlon']);
1255 $aResultPlaceIDs = array($iPlaceID);
1256 // TODO: this needs refactoring!
1258 // Get the details for display (is this a redundant extra step?)
1259 $sPlaceIDs = join(',',$aResultPlaceIDs);
1260 $sOrderSQL = 'CASE ';
1261 foreach(array_keys($aResultPlaceIDs) as $iOrder => $iPlaceID)
1263 $sOrderSQL .= 'when min(place_id) = '.$iPlaceID.' then '.$iOrder.' ';
1265 $sOrderSQL .= ' ELSE 10000000 END';
1266 $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,";
1267 $sSQL .= "get_address_by_language(place_id, $sLanguagePrefArraySQL) as langaddress,";
1268 $sSQL .= "get_name_by_language(name, $sLanguagePrefArraySQL) as placename,";
1269 $sSQL .= "get_name_by_language(name, ARRAY['ref']) as ref,";
1270 $sSQL .= "avg(ST_X(centroid)) as lon,avg(ST_Y(centroid)) as lat, ";
1271 //$sSQL .= $sOrderSQL." as porder, ";
1272 $sSQL .= "coalesce(importance,0.75-(rank_search::float/40)) as importance, ";
1273 $sSQL .= "(extratags->'place') as extra_place ";
1274 $sSQL .= "from placex where place_id in ($sPlaceIDs) ";
1275 $sSQL .= "and (placex.rank_address between $iMinAddressRank and $iMaxAddressRank ";
1276 if (14 >= $iMinAddressRank && 14 <= $iMaxAddressRank) $sSQL .= " OR (extratags->'place') = 'city'";
1277 if ($aAddressRankList) $sSQL .= " OR placex.rank_address in (".join(',',$aAddressRankList).")";
1279 $sSQL .= "group by osm_type,osm_id,class,type,admin_level,rank_search,rank_address,calculated_country_code,importance";
1280 if (!$bDeDupe) $sSQL .= ",place_id";
1281 $sSQL .= ",get_address_by_language(place_id, $sLanguagePrefArraySQL) ";
1282 $sSQL .= ",get_name_by_language(name, $sLanguagePrefArraySQL) ";
1283 $sSQL .= ",get_name_by_language(name, ARRAY['ref']) ";
1284 $sSQL .= ",extratags->'place' ";
1286 $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,";
1287 $sSQL .= "get_address_by_language(place_id, $sLanguagePrefArraySQL) as langaddress,";
1288 $sSQL .= "null as placename,";
1289 $sSQL .= "null as ref,";
1290 $sSQL .= "avg(ST_X(centroid)) as lon,avg(ST_Y(centroid)) as lat, ";
1291 //$sSQL .= $sOrderSQL." as porder, ";
1292 $sSQL .= "-0.15 as importance, ";
1293 $sSQL .= "null as extra_place ";
1294 $sSQL .= "from location_property_tiger where place_id in ($sPlaceIDs) ";
1295 $sSQL .= "and 30 between $iMinAddressRank and $iMaxAddressRank ";
1296 $sSQL .= "group by place_id";
1297 if (!$bDeDupe) $sSQL .= ",place_id";
1299 $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,";
1300 $sSQL .= "get_address_by_language(place_id, $sLanguagePrefArraySQL) as langaddress,";
1301 $sSQL .= "null as placename,";
1302 $sSQL .= "null as ref,";
1303 $sSQL .= "avg(ST_X(centroid)) as lon,avg(ST_Y(centroid)) as lat, ";
1304 //$sSQL .= $sOrderSQL." as porder, ";
1305 $sSQL .= "-0.10 as importance, ";
1306 $sSQL .= "null as extra_place ";
1307 $sSQL .= "from location_property_aux where place_id in ($sPlaceIDs) ";
1308 $sSQL .= "and 30 between $iMinAddressRank and $iMaxAddressRank ";
1309 $sSQL .= "group by place_id";
1310 if (!$bDeDupe) $sSQL .= ",place_id";
1311 $sSQL .= ",get_address_by_language(place_id, $sLanguagePrefArraySQL) ";
1312 $sSQL .= "order by importance desc";
1313 //$sSQL .= "order by rank_search,rank_address,porder asc";
1314 if (CONST_Debug) { echo "<hr>", var_dump($sSQL); }
1315 $aSearchResults = $oDB->getAll($sSQL);
1316 //var_dump($sSQL,$aSearchResults);exit;
1318 if (PEAR::IsError($aSearchResults))
1320 failInternalError("Could not get details for place (near).", $sSQL, $aSearchResults);
1325 $aSearchResults = array();
1331 $sSearchResult = '';
1332 if (!sizeof($aSearchResults) && isset($_GET['q']) && $_GET['q'])
1334 $sSearchResult = 'No Results Found';
1336 //var_Dump($aSearchResults);
1338 $aClassType = getClassTypesWithImportance();
1339 $aRecheckWords = preg_split('/\b/u',$sQuery);
1340 foreach($aRecheckWords as $i => $sWord)
1342 if (!$sWord) unset($aRecheckWords[$i]);
1344 foreach($aSearchResults as $iResNum => $aResult)
1346 if (CONST_Search_AreaPolygons)
1348 // Get the bounding box and outline polygon
1349 $sSQL = "select place_id,numfeatures,area,outline,";
1350 $sSQL .= "ST_Y(ST_PointN(ST_ExteriorRing(Box2D(outline)),4)) as minlat,ST_Y(ST_PointN(ST_ExteriorRing(Box2D(outline)),2)) as maxlat,";
1351 $sSQL .= "ST_X(ST_PointN(ST_ExteriorRing(Box2D(outline)),1)) as minlon,ST_X(ST_PointN(ST_ExteriorRing(Box2D(outline)),3)) as maxlon,";
1352 $sSQL .= "ST_AsText(outline) as outlinestring from get_place_boundingbox_quick(".$aResult['place_id'].")";
1354 $sSQL = "select place_id,0 as numfeatures,st_area(geometry) as area,";
1355 $sSQL .= "ST_Y(centroid) as centrelat,ST_X(centroid) as centrelon,";
1356 $sSQL .= "ST_Y(ST_PointN(ST_ExteriorRing(Box2D(geometry)),4)) as minlat,ST_Y(ST_PointN(ST_ExteriorRing(Box2D(geometry)),2)) as maxlat,";
1357 $sSQL .= "ST_X(ST_PointN(ST_ExteriorRing(Box2D(geometry)),1)) as minlon,ST_X(ST_PointN(ST_ExteriorRing(Box2D(geometry)),3)) as maxlon";
1358 if ($bAsGeoJSON) $sSQL .= ",ST_AsGeoJSON(geometry) as asgeojson";
1359 if ($bAsKML) $sSQL .= ",ST_AsKML(geometry) as askml";
1360 if ($bAsSVG) $sSQL .= ",ST_AsSVG(geometry) as assvg";
1361 if ($bAsText || $bShowPolygons) $sSQL .= ",ST_AsText(geometry) as astext";
1362 $sSQL .= " from placex where place_id = ".$aResult['place_id'].' and st_geometrytype(Box2D(geometry)) = \'ST_Polygon\'';
1363 $aPointPolygon = $oDB->getRow($sSQL);
1364 if (PEAR::IsError($aPointPolygon))
1366 failInternalError("Could not get outline.", $sSQL, $aPointPolygon);
1368 if ($aPointPolygon['place_id'])
1370 if ($bAsGeoJSON) $aResult['asgeojson'] = $aPointPolygon['asgeojson'];
1371 if ($bAsKML) $aResult['askml'] = $aPointPolygon['askml'];
1372 if ($bAsSVG) $aResult['assvg'] = $aPointPolygon['assvg'];
1373 if ($bAsText) $aResult['astext'] = $aPointPolygon['astext'];
1375 if ($aPointPolygon['centrelon'] !== null && $aPointPolygon['centrelat'] !== null )
1377 $aResult['lat'] = $aPointPolygon['centrelat'];
1378 $aResult['lon'] = $aPointPolygon['centrelon'];
1382 // Translate geometary string to point array
1383 if (preg_match('#POLYGON\\(\\(([- 0-9.,]+)#',$aPointPolygon['astext'],$aMatch))
1385 preg_match_all('/(-?[0-9.]+) (-?[0-9.]+)/',$aMatch[1],$aPolyPoints,PREG_SET_ORDER);
1387 elseif (preg_match('#MULTIPOLYGON\\(\\(\\(([- 0-9.,]+)#',$aPointPolygon['astext'],$aMatch))
1389 preg_match_all('/(-?[0-9.]+) (-?[0-9.]+)/',$aMatch[1],$aPolyPoints,PREG_SET_ORDER);
1391 elseif (preg_match('#POINT\\((-?[0-9.]+) (-?[0-9.]+)\\)#',$aPointPolygon['astext'],$aMatch))
1394 $iSteps = ($fRadius * 40000)^2;
1395 $fStepSize = (2*pi())/$iSteps;
1396 $aPolyPoints = array();
1397 for($f = 0; $f < 2*pi(); $f += $fStepSize)
1399 $aPolyPoints[] = array('',$aMatch[1]+($fRadius*sin($f)),$aMatch[2]+($fRadius*cos($f)));
1401 $aPointPolygon['minlat'] = $aPointPolygon['minlat'] - $fRadius;
1402 $aPointPolygon['maxlat'] = $aPointPolygon['maxlat'] + $fRadius;
1403 $aPointPolygon['minlon'] = $aPointPolygon['minlon'] - $fRadius;
1404 $aPointPolygon['maxlon'] = $aPointPolygon['maxlon'] + $fRadius;
1408 // Output data suitable for display (points and a bounding box)
1409 if ($bShowPolygons && isset($aPolyPoints))
1411 $aResult['aPolyPoints'] = array();
1412 foreach($aPolyPoints as $aPoint)
1414 $aResult['aPolyPoints'][] = array($aPoint[1], $aPoint[2]);
1417 $aResult['aBoundingBox'] = array($aPointPolygon['minlat'],$aPointPolygon['maxlat'],$aPointPolygon['minlon'],$aPointPolygon['maxlon']);
1421 if ($aResult['extra_place'] == 'city')
1423 $aResult['class'] = 'place';
1424 $aResult['type'] = 'city';
1425 $aResult['rank_search'] = 16;
1428 if (!isset($aResult['aBoundingBox']))
1431 $fDiameter = 0.0001;
1433 if (isset($aClassType[$aResult['class'].':'.$aResult['type'].':'.$aResult['admin_level']]['defdiameter'])
1434 && $aClassType[$aResult['class'].':'.$aResult['type'].':'.$aResult['admin_level']]['defdiameter'])
1436 $fDiameter = $aClassType[$aResult['class'].':'.$aResult['type'].':'.$aResult['admin_level']]['defzoom'];
1438 elseif (isset($aClassType[$aResult['class'].':'.$aResult['type']]['defdiameter'])
1439 && $aClassType[$aResult['class'].':'.$aResult['type']]['defdiameter'])
1441 $fDiameter = $aClassType[$aResult['class'].':'.$aResult['type']]['defdiameter'];
1443 $fRadius = $fDiameter / 2;
1445 $iSteps = max(8,min(100,$fRadius * 3.14 * 100000));
1446 $fStepSize = (2*pi())/$iSteps;
1447 $aPolyPoints = array();
1448 for($f = 0; $f < 2*pi(); $f += $fStepSize)
1450 $aPolyPoints[] = array('',$aResult['lon']+($fRadius*sin($f)),$aResult['lat']+($fRadius*cos($f)));
1452 $aPointPolygon['minlat'] = $aResult['lat'] - $fRadius;
1453 $aPointPolygon['maxlat'] = $aResult['lat'] + $fRadius;
1454 $aPointPolygon['minlon'] = $aResult['lon'] - $fRadius;
1455 $aPointPolygon['maxlon'] = $aResult['lon'] + $fRadius;
1457 // Output data suitable for display (points and a bounding box)
1460 $aResult['aPolyPoints'] = array();
1461 foreach($aPolyPoints as $aPoint)
1463 $aResult['aPolyPoints'][] = array($aPoint[1], $aPoint[2]);
1466 $aResult['aBoundingBox'] = array($aPointPolygon['minlat'],$aPointPolygon['maxlat'],$aPointPolygon['minlon'],$aPointPolygon['maxlon']);
1469 // Is there an icon set for this type of result?
1470 if (isset($aClassType[$aResult['class'].':'.$aResult['type']]['icon'])
1471 && $aClassType[$aResult['class'].':'.$aResult['type']]['icon'])
1473 $aResult['icon'] = CONST_Website_BaseURL.'images/mapicons/'.$aClassType[$aResult['class'].':'.$aResult['type']]['icon'].'.p.20.png';
1476 if (isset($aClassType[$aResult['class'].':'.$aResult['type']]['label'])
1477 && $aClassType[$aResult['class'].':'.$aResult['type']]['label'])
1479 $aResult['label'] = $aClassType[$aResult['class'].':'.$aResult['type']]['label'];
1482 if ($bShowAddressDetails)
1484 $aResult['address'] = getAddressDetails($oDB, $sLanguagePrefArraySQL, $aResult['place_id'], $aResult['country_code']);
1485 if ($aResult['extra_place'] == 'city' && !isset($aResult['address']['city']))
1487 $aResult['address'] = array_merge(array('city' => array_shift(array_values($aResult['address']))), $aResult['address']);
1490 //var_dump($aResult['address']);
1494 // Adjust importance for the number of exact string matches in the result
1495 $aResult['importance'] = max(0.001,$aResult['importance']);
1497 $sAddress = $aResult['langaddress'];
1498 foreach($aRecheckWords as $i => $sWord)
1500 if (stripos($sAddress, $sWord)!==false) $iCountWords++;
1503 $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
1505 //if (CONST_Debug) var_dump($aResult['class'].':'.$aResult['type'].':'.$aResult['admin_level']);
1507 if (isset($aClassType[$aResult['class'].':'.$aResult['type'].':'.$aResult['admin_level']]['importance'])
1508 && $aClassType[$aResult['class'].':'.$aResult['type'].':'.$aResult['admin_level']]['importance'])
1510 $aResult['importance'] = $aClassType[$aResult['class'].':'.$aResult['type'].':'.$aResult['admin_level']]['importance'];
1512 elseif (isset($aClassType[$aResult['class'].':'.$aResult['type']]['importance'])
1513 && $aClassType[$aResult['class'].':'.$aResult['type']]['importance'])
1515 $aResult['importance'] = $aClassType[$aResult['class'].':'.$aResult['type']]['importance'];
1519 $aResult['importance'] = 1000000000000000;
1522 $aResult['name'] = $aResult['langaddress'];
1523 $aResult['foundorder'] = -$aResult['addressimportance'];
1524 $aSearchResults[$iResNum] = $aResult;
1526 uasort($aSearchResults, 'byImportance');
1528 $aOSMIDDone = array();
1529 $aClassTypeNameDone = array();
1530 $aToFilter = $aSearchResults;
1531 $aSearchResults = array();
1534 foreach($aToFilter as $iResNum => $aResult)
1536 if ($aResult['type'] == 'adminitrative') $aResult['type'] = 'administrative';
1537 $aExcludePlaceIDs[$aResult['place_id']] = $aResult['place_id'];
1540 $fLat = $aResult['lat'];
1541 $fLon = $aResult['lon'];
1542 if (isset($aResult['zoom'])) $iZoom = $aResult['zoom'];
1545 if (!$bDeDupe || (!isset($aOSMIDDone[$aResult['osm_type'].$aResult['osm_id']])
1546 && !isset($aClassTypeNameDone[$aResult['osm_type'].$aResult['class'].$aResult['type'].$aResult['name'].$aResult['admin_level']])))
1548 $aOSMIDDone[$aResult['osm_type'].$aResult['osm_id']] = true;
1549 $aClassTypeNameDone[$aResult['osm_type'].$aResult['class'].$aResult['type'].$aResult['name'].$aResult['admin_level']] = true;
1550 $aSearchResults[] = $aResult;
1553 // Absolute limit on number of results
1554 if (sizeof($aSearchResults) >= $iFinalLimit) break;
1557 $sDataDate = $oDB->getOne("select TO_CHAR(lastimportdate - '2 minutes'::interval,'YYYY/MM/DD HH24:MI')||' GMT' from import_status limit 1");
1559 if (isset($_GET['nearlat']) && isset($_GET['nearlon']))
1561 $sQuery .= ' ['.$_GET['nearlat'].','.$_GET['nearlon'].']';
1566 logEnd($oDB, $hLog, sizeof($aToFilter));
1568 $sMoreURL = CONST_Website_BaseURL.'search?format='.urlencode($sOutputFormat).'&exclude_place_ids='.join(',',$aExcludePlaceIDs);
1569 if (isset($_SERVER["HTTP_ACCEPT_LANGUAGE"])) $sMoreURL .= '&accept-language='.$_SERVER["HTTP_ACCEPT_LANGUAGE"];
1570 if ($bShowPolygons) $sMoreURL .= '&polygon=1';
1571 if ($bShowAddressDetails) $sMoreURL .= '&addressdetails=1';
1572 if (isset($_GET['viewbox']) && $_GET['viewbox']) $sMoreURL .= '&viewbox='.urlencode($_GET['viewbox']);
1573 if (isset($_GET['nearlat']) && isset($_GET['nearlon'])) $sMoreURL .= '&nearlat='.(float)$_GET['nearlat'].'&nearlon='.(float)$_GET['nearlon'];
1574 $sMoreURL .= '&q='.urlencode($sQuery);
1576 if (CONST_Debug) exit;
1578 include(CONST_BasePath.'/lib/template/search-'.$sOutputFormat.'.php');