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']))
934 $aOrder[] = '(select count(*) from (select unnest(ARRAY['.join($aSearch['aFullNameAddress'],",").']) INTERSECT select unnest(nameaddress_vector))s) DESC';
938 $sSQL = "select place_id";
939 $sSQL .= " from search_name";
940 $sSQL .= " where ".join(' and ',$aTerms);
941 $sSQL .= " order by ".join(', ',$aOrder);
942 if ($aSearch['sHouseNumber'] || $aSearch['sClass'])
943 $sSQL .= " limit 50";
944 elseif (!sizeof($aSearch['aName']) && !sizeof($aSearch['aAddress']) && $aSearch['sClass'])
947 $sSQL .= " limit ".$iLimit;
949 if (CONST_Debug) { var_dump($sSQL); }
950 $aViewBoxPlaceIDs = $oDB->getAll($sSQL);
951 if (PEAR::IsError($aViewBoxPlaceIDs))
953 failInternalError("Could not get places for search terms.", $sSQL, $aViewBoxPlaceIDs);
955 //var_dump($aViewBoxPlaceIDs);
956 // Did we have an viewbox matches?
957 $aPlaceIDs = array();
958 $bViewBoxMatch = false;
959 foreach($aViewBoxPlaceIDs as $aViewBoxRow)
961 //if ($bViewBoxMatch == 1 && $aViewBoxRow['in_small'] == 'f') break;
962 //if ($bViewBoxMatch == 2 && $aViewBoxRow['in_large'] == 'f') break;
963 //if ($aViewBoxRow['in_small'] == 't') $bViewBoxMatch = 1;
964 //else if ($aViewBoxRow['in_large'] == 't') $bViewBoxMatch = 2;
965 $aPlaceIDs[] = $aViewBoxRow['place_id'];
968 //var_Dump($aPlaceIDs);
971 if ($aSearch['sHouseNumber'] && sizeof($aPlaceIDs))
973 $aRoadPlaceIDs = $aPlaceIDs;
974 $sPlaceIDs = join(',',$aPlaceIDs);
976 // Now they are indexed look for a house attached to a street we found
977 $sHouseNumberRegex = '\\\\m'.str_replace(' ','[-,/ ]',$aSearch['sHouseNumber']).'\\\\M';
978 $sSQL = "select place_id from placex where parent_place_id in (".$sPlaceIDs.") and housenumber ~* E'".$sHouseNumberRegex."'";
979 if (sizeof($aExcludePlaceIDs))
981 $sSQL .= " and place_id not in (".join(',',$aExcludePlaceIDs).")";
983 $sSQL .= " limit $iLimit";
984 if (CONST_Debug) var_dump($sSQL);
985 $aPlaceIDs = $oDB->getCol($sSQL);
987 // If not try the aux fallback table
988 if (!sizeof($aPlaceIDs))
990 $sSQL = "select place_id from location_property_aux where parent_place_id in (".$sPlaceIDs.") and housenumber = '".pg_escape_string($aSearch['sHouseNumber'])."'";
991 if (sizeof($aExcludePlaceIDs))
993 $sSQL .= " and place_id not in (".join(',',$aExcludePlaceIDs).")";
995 //$sSQL .= " limit $iLimit";
996 if (CONST_Debug) var_dump($sSQL);
997 $aPlaceIDs = $oDB->getCol($sSQL);
1000 if (!sizeof($aPlaceIDs))
1002 $sSQL = "select place_id from location_property_tiger where parent_place_id in (".$sPlaceIDs.") and housenumber = '".pg_escape_string($aSearch['sHouseNumber'])."'";
1003 if (sizeof($aExcludePlaceIDs))
1005 $sSQL .= " and place_id not in (".join(',',$aExcludePlaceIDs).")";
1007 //$sSQL .= " limit $iLimit";
1008 if (CONST_Debug) var_dump($sSQL);
1009 $aPlaceIDs = $oDB->getCol($sSQL);
1012 // Fallback to the road
1013 if (!sizeof($aPlaceIDs) && preg_match('/[0-9]+/', $aSearch['sHouseNumber']))
1015 $aPlaceIDs = $aRoadPlaceIDs;
1020 if ($aSearch['sClass'] && sizeof($aPlaceIDs))
1022 $sPlaceIDs = join(',',$aPlaceIDs);
1023 $aClassPlaceIDs = array();
1025 if (!$aSearch['sOperator'] || $aSearch['sOperator'] == 'name')
1027 // If they were searching for a named class (i.e. 'Kings Head pub') then we might have an extra match
1028 $sSQL = "select place_id from placex where place_id in ($sPlaceIDs) and class='".$aSearch['sClass']."' and type='".$aSearch['sType']."'";
1029 $sSQL .= " and linked_place_id is null";
1030 if ($sCountryCodesSQL) $sSQL .= " and calculated_country_code in ($sCountryCodesSQL)";
1031 $sSQL .= " order by rank_search asc limit $iLimit";
1032 if (CONST_Debug) var_dump($sSQL);
1033 $aClassPlaceIDs = $oDB->getCol($sSQL);
1036 if (!$aSearch['sOperator'] || $aSearch['sOperator'] == 'near') // & in
1038 $sSQL = "select count(*) from pg_tables where tablename = 'place_classtype_".$aSearch['sClass']."_".$aSearch['sType']."'";
1039 $bCacheTable = $oDB->getOne($sSQL);
1041 $sSQL = "select min(rank_search) from placex where place_id in ($sPlaceIDs)";
1043 if (CONST_Debug) var_dump($sSQL);
1044 $iMaxRank = ((int)$oDB->getOne($sSQL));
1046 // For state / country level searches the normal radius search doesn't work very well
1047 $sPlaceGeom = false;
1048 if ($iMaxRank < 9 && $bCacheTable)
1050 // Try and get a polygon to search in instead
1051 $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";
1052 if (CONST_Debug) var_dump($sSQL);
1053 $sPlaceGeom = $oDB->getOne($sSQL);
1063 $sSQL = "select place_id from placex where place_id in ($sPlaceIDs) and rank_search < $iMaxRank";
1064 if (CONST_Debug) var_dump($sSQL);
1065 $aPlaceIDs = $oDB->getCol($sSQL);
1066 $sPlaceIDs = join(',',$aPlaceIDs);
1069 if ($sPlaceIDs || $sPlaceGeom)
1075 // More efficient - can make the range bigger
1079 if ($sNearPointSQL) $sOrderBySQL = "ST_Distance($sNearPointSQL, l.centroid)";
1080 else if ($sPlaceIDs) $sOrderBySQL = "ST_Distance(l.centroid, f.geometry)";
1081 else if ($sPlaceGeom) $sOrderBysSQL = "ST_Distance(st_centroid('".$sPlaceGeom."'), l.centroid)";
1083 $sSQL = "select distinct l.place_id".($sOrderBySQL?','.$sOrderBySQL:'')." from place_classtype_".$aSearch['sClass']."_".$aSearch['sType']." as l";
1084 if ($sCountryCodesSQL) $sSQL .= " join placex as lp using (place_id)";
1087 $sSQL .= ",placex as f where ";
1088 $sSQL .= "f.place_id in ($sPlaceIDs) and ST_DWithin(l.centroid, f.centroid, $fRange) ";
1093 $sSQL .= "ST_Contains('".$sPlaceGeom."', l.centroid) ";
1095 if (sizeof($aExcludePlaceIDs))
1097 $sSQL .= " and l.place_id not in (".join(',',$aExcludePlaceIDs).")";
1099 if ($sCountryCodesSQL) $sSQL .= " and lp.calculated_country_code in ($sCountryCodesSQL)";
1100 if ($sOrderBySQL) $sSQL .= "order by ".$sOrderBySQL." asc";
1101 if ($iOffset) $sSQL .= " offset $iOffset";
1102 $sSQL .= " limit $iLimit";
1103 if (CONST_Debug) var_dump($sSQL);
1104 $aClassPlaceIDs = array_merge($aClassPlaceIDs, $oDB->getCol($sSQL));
1108 if (isset($aSearch['fRadius']) && $aSearch['fRadius']) $fRange = $aSearch['fRadius'];
1111 if ($sNearPointSQL) $sOrderBySQL = "ST_Distance($sNearPointSQL, l.geometry)";
1112 else $sOrderBySQL = "ST_Distance(l.geometry, f.geometry)";
1114 $sSQL = "select distinct l.place_id".($sOrderBysSQL?','.$sOrderBysSQL:'')." from placex as l,placex as f where ";
1115 $sSQL .= "f.place_id in ( $sPlaceIDs) and ST_DWithin(l.geometry, f.centroid, $fRange) ";
1116 $sSQL .= "and l.class='".$aSearch['sClass']."' and l.type='".$aSearch['sType']."' ";
1117 if (sizeof($aExcludePlaceIDs))
1119 $sSQL .= " and l.place_id not in (".join(',',$aExcludePlaceIDs).")";
1121 if ($sCountryCodesSQL) $sSQL .= " and l.calculated_country_code in ($sCountryCodesSQL)";
1122 if ($sOrderBy) $sSQL .= "order by ".$OrderBysSQL." asc";
1123 if ($iOffset) $sSQL .= " offset $iOffset";
1124 $sSQL .= " limit $iLimit";
1125 if (CONST_Debug) var_dump($sSQL);
1126 $aClassPlaceIDs = array_merge($aClassPlaceIDs, $oDB->getCol($sSQL));
1131 $aPlaceIDs = $aClassPlaceIDs;
1137 if (PEAR::IsError($aPlaceIDs))
1139 failInternalError("Could not get place IDs from tokens." ,$sSQL, $aPlaceIDs);
1142 if (CONST_Debug) { echo "<br><b>Place IDs:</b> "; var_Dump($aPlaceIDs); }
1144 foreach($aPlaceIDs as $iPlaceID)
1146 $aResultPlaceIDs[$iPlaceID] = $iPlaceID;
1148 if ($iQueryLoop > 20) break;
1152 if (isset($aResultPlaceIDs) && sizeof($aResultPlaceIDs)) break;
1153 if ($iGroupLoop > 4) break;
1154 if ($iQueryLoop > 30) break;
1157 // Did we find anything?
1158 if (isset($aResultPlaceIDs) && sizeof($aResultPlaceIDs))
1160 //var_Dump($aResultPlaceIDs);exit;
1161 // Get the details for display (is this a redundant extra step?)
1162 $sPlaceIDs = join(',',$aResultPlaceIDs);
1163 $sOrderSQL = 'CASE ';
1164 foreach(array_keys($aResultPlaceIDs) as $iOrder => $iPlaceID)
1166 $sOrderSQL .= 'when min(place_id) = '.$iPlaceID.' then '.$iOrder.' ';
1168 $sOrderSQL .= ' ELSE 10000000 END';
1169 $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,";
1170 $sSQL .= "get_address_by_language(place_id, $sLanguagePrefArraySQL) as langaddress,";
1171 $sSQL .= "get_name_by_language(name, $sLanguagePrefArraySQL) as placename,";
1172 $sSQL .= "get_name_by_language(name, ARRAY['ref']) as ref,";
1173 $sSQL .= "avg(ST_X(centroid)) as lon,avg(ST_Y(centroid)) as lat, ";
1174 //$sSQL .= $sOrderSQL." as porder, ";
1175 $sSQL .= "coalesce(importance,0.75-(rank_search::float/40)) as importance, ";
1176 $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, ";
1177 $sSQL .= "(extratags->'place') as extra_place ";
1178 $sSQL .= "from placex where place_id in ($sPlaceIDs) ";
1179 $sSQL .= "and (placex.rank_address between $iMinAddressRank and $iMaxAddressRank ";
1180 if (14 >= $iMinAddressRank && 14 <= $iMaxAddressRank) $sSQL .= " OR (extratags->'place') = 'city'";
1181 if ($aAddressRankList) $sSQL .= " OR placex.rank_address in (".join(',',$aAddressRankList).")";
1183 if ($sAllowedTypesSQLList) $sSQL .= "and placex.class in $sAllowedTypesSQLList ";
1184 $sSQL .= "and linked_place_id is null ";
1185 $sSQL .= "group by osm_type,osm_id,class,type,admin_level,rank_search,rank_address,calculated_country_code,importance";
1186 if (!$bDeDupe) $sSQL .= ",place_id";
1187 $sSQL .= ",langaddress ";
1188 $sSQL .= ",placename ";
1190 $sSQL .= ",extratags->'place' ";
1192 $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,";
1193 $sSQL .= "get_address_by_language(place_id, $sLanguagePrefArraySQL) as langaddress,";
1194 $sSQL .= "null as placename,";
1195 $sSQL .= "null as ref,";
1196 $sSQL .= "avg(ST_X(centroid)) as lon,avg(ST_Y(centroid)) as lat, ";
1197 //$sSQL .= $sOrderSQL." as porder, ";
1198 $sSQL .= "-0.15 as importance, ";
1199 $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, ";
1200 $sSQL .= "null as extra_place ";
1201 $sSQL .= "from location_property_tiger where place_id in ($sPlaceIDs) ";
1202 $sSQL .= "and 30 between $iMinAddressRank and $iMaxAddressRank ";
1203 $sSQL .= "group by place_id";
1204 if (!$bDeDupe) $sSQL .= ",place_id";
1206 $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,";
1207 $sSQL .= "get_address_by_language(place_id, $sLanguagePrefArraySQL) as langaddress,";
1208 $sSQL .= "null as placename,";
1209 $sSQL .= "null as ref,";
1210 $sSQL .= "avg(ST_X(centroid)) as lon,avg(ST_Y(centroid)) as lat, ";
1211 //$sSQL .= $sOrderSQL." as porder, ";
1212 $sSQL .= "-0.10 as importance, ";
1213 $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, ";
1214 $sSQL .= "null as extra_place ";
1215 $sSQL .= "from location_property_aux where place_id in ($sPlaceIDs) ";
1216 $sSQL .= "and 30 between $iMinAddressRank and $iMaxAddressRank ";
1217 $sSQL .= "group by place_id";
1218 if (!$bDeDupe) $sSQL .= ",place_id";
1219 $sSQL .= ",get_address_by_language(place_id, $sLanguagePrefArraySQL) ";
1220 $sSQL .= "order by importance desc";
1221 //$sSQL .= "order by rank_search,rank_address,porder asc";
1222 if (CONST_Debug) { echo "<hr>"; var_dump($sSQL); }
1223 $aSearchResults = $oDB->getAll($sSQL);
1224 //var_dump($sSQL,$aSearchResults);exit;
1226 if (PEAR::IsError($aSearchResults))
1228 failInternalError("Could not get details for place.", $sSQL, $aSearchResults);
1231 } // end if ($sQuery)
1234 if (isset($_GET['nearlat']) && trim($_GET['nearlat'])!=='' && isset($_GET['nearlon']) && trim($_GET['nearlon']) !== '')
1236 $iPlaceID = geocodeReverse((float)$_GET['nearlat'], (float)$_GET['nearlon']);
1240 $aResultPlaceIDs = array($iPlaceID);
1241 // TODO: this needs refactoring!
1243 // Get the details for display (is this a redundant extra step?)
1244 $sPlaceIDs = join(',',$aResultPlaceIDs);
1245 $sOrderSQL = 'CASE ';
1246 foreach(array_keys($aResultPlaceIDs) as $iOrder => $iPlaceID)
1248 $sOrderSQL .= 'when min(place_id) = '.$iPlaceID.' then '.$iOrder.' ';
1250 $sOrderSQL .= ' ELSE 10000000 END';
1251 $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,";
1252 $sSQL .= "get_address_by_language(place_id, $sLanguagePrefArraySQL) as langaddress,";
1253 $sSQL .= "get_name_by_language(name, $sLanguagePrefArraySQL) as placename,";
1254 $sSQL .= "get_name_by_language(name, ARRAY['ref']) as ref,";
1255 $sSQL .= "avg(ST_X(centroid)) as lon,avg(ST_Y(centroid)) as lat, ";
1256 //$sSQL .= $sOrderSQL." as porder, ";
1257 $sSQL .= "coalesce(importance,0.75-(rank_search::float/40)) as importance, ";
1258 $sSQL .= "(extratags->'place') as extra_place ";
1259 $sSQL .= "from placex where place_id in ($sPlaceIDs) ";
1260 $sSQL .= "and (placex.rank_address between $iMinAddressRank and $iMaxAddressRank ";
1261 if (14 >= $iMinAddressRank && 14 <= $iMaxAddressRank) $sSQL .= " OR (extratags->'place') = 'city'";
1262 if ($aAddressRankList) $sSQL .= " OR placex.rank_address in (".join(',',$aAddressRankList).")";
1264 $sSQL .= "group by osm_type,osm_id,class,type,admin_level,rank_search,rank_address,calculated_country_code,importance";
1265 if (!$bDeDupe) $sSQL .= ",place_id";
1266 $sSQL .= ",get_address_by_language(place_id, $sLanguagePrefArraySQL) ";
1267 $sSQL .= ",get_name_by_language(name, $sLanguagePrefArraySQL) ";
1268 $sSQL .= ",get_name_by_language(name, ARRAY['ref']) ";
1269 $sSQL .= ",extratags->'place' ";
1271 $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,";
1272 $sSQL .= "get_address_by_language(place_id, $sLanguagePrefArraySQL) as langaddress,";
1273 $sSQL .= "null as placename,";
1274 $sSQL .= "null as ref,";
1275 $sSQL .= "avg(ST_X(centroid)) as lon,avg(ST_Y(centroid)) as lat, ";
1276 //$sSQL .= $sOrderSQL." as porder, ";
1277 $sSQL .= "-0.15 as importance, ";
1278 $sSQL .= "null as extra_place ";
1279 $sSQL .= "from location_property_tiger where place_id in ($sPlaceIDs) ";
1280 $sSQL .= "and 30 between $iMinAddressRank and $iMaxAddressRank ";
1281 $sSQL .= "group by place_id";
1282 if (!$bDeDupe) $sSQL .= ",place_id";
1284 $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,";
1285 $sSQL .= "get_address_by_language(place_id, $sLanguagePrefArraySQL) as langaddress,";
1286 $sSQL .= "null as placename,";
1287 $sSQL .= "null as ref,";
1288 $sSQL .= "avg(ST_X(centroid)) as lon,avg(ST_Y(centroid)) as lat, ";
1289 //$sSQL .= $sOrderSQL." as porder, ";
1290 $sSQL .= "-0.10 as importance, ";
1291 $sSQL .= "null as extra_place ";
1292 $sSQL .= "from location_property_aux where place_id in ($sPlaceIDs) ";
1293 $sSQL .= "and 30 between $iMinAddressRank and $iMaxAddressRank ";
1294 $sSQL .= "group by place_id";
1295 if (!$bDeDupe) $sSQL .= ",place_id";
1296 $sSQL .= ",get_address_by_language(place_id, $sLanguagePrefArraySQL) ";
1297 $sSQL .= "order by importance desc";
1298 //$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 (near).", $sSQL, $aSearchResults);
1310 $aSearchResults = array();
1316 $sSearchResult = '';
1317 if (!sizeof($aSearchResults) && isset($_GET['q']) && $_GET['q'])
1319 $sSearchResult = 'No Results Found';
1321 //var_Dump($aSearchResults);
1323 $aClassType = getClassTypesWithImportance();
1324 $aRecheckWords = preg_split('/\b/u',$sQuery);
1325 foreach($aRecheckWords as $i => $sWord)
1327 if (!$sWord) unset($aRecheckWords[$i]);
1329 foreach($aSearchResults as $iResNum => $aResult)
1331 if (CONST_Search_AreaPolygons)
1333 // Get the bounding box and outline polygon
1334 $sSQL = "select place_id,numfeatures,area,outline,";
1335 $sSQL .= "ST_Y(ST_PointN(ST_ExteriorRing(Box2D(outline)),4)) as minlat,ST_Y(ST_PointN(ST_ExteriorRing(Box2D(outline)),2)) as maxlat,";
1336 $sSQL .= "ST_X(ST_PointN(ST_ExteriorRing(Box2D(outline)),1)) as minlon,ST_X(ST_PointN(ST_ExteriorRing(Box2D(outline)),3)) as maxlon,";
1337 $sSQL .= "ST_AsText(outline) as outlinestring from get_place_boundingbox_quick(".$aResult['place_id'].")";
1339 $sSQL = "select place_id,0 as numfeatures,st_area(geometry) as area,";
1340 $sSQL .= "ST_Y(centroid) as centrelat,ST_X(centroid) as centrelon,";
1341 $sSQL .= "ST_Y(ST_PointN(ST_ExteriorRing(Box2D(geometry)),4)) as minlat,ST_Y(ST_PointN(ST_ExteriorRing(Box2D(geometry)),2)) as maxlat,";
1342 $sSQL .= "ST_X(ST_PointN(ST_ExteriorRing(Box2D(geometry)),1)) as minlon,ST_X(ST_PointN(ST_ExteriorRing(Box2D(geometry)),3)) as maxlon";
1343 if ($bAsGeoJSON) $sSQL .= ",ST_AsGeoJSON(geometry) as asgeojson";
1344 if ($bAsKML) $sSQL .= ",ST_AsKML(geometry) as askml";
1345 if ($bAsSVG) $sSQL .= ",ST_AsSVG(geometry) as assvg";
1346 if ($bAsText || $bShowPolygons) $sSQL .= ",ST_AsText(geometry) as astext";
1347 $sSQL .= " from placex where place_id = ".$aResult['place_id'].' and st_geometrytype(Box2D(geometry)) = \'ST_Polygon\'';
1348 $aPointPolygon = $oDB->getRow($sSQL);
1349 if (PEAR::IsError($aPointPolygon))
1351 failInternalError("Could not get outline.", $sSQL, $aPointPolygon);
1353 if ($aPointPolygon['place_id'])
1355 if ($bAsGeoJSON) $aResult['asgeojson'] = $aPointPolygon['asgeojson'];
1356 if ($bAsKML) $aResult['askml'] = $aPointPolygon['askml'];
1357 if ($bAsSVG) $aResult['assvg'] = $aPointPolygon['assvg'];
1358 if ($bAsText) $aResult['astext'] = $aPointPolygon['astext'];
1360 if ($aPointPolygon['centrelon'] !== null && $aPointPolygon['centrelat'] !== null )
1362 $aResult['lat'] = $aPointPolygon['centrelat'];
1363 $aResult['lon'] = $aPointPolygon['centrelon'];
1367 // Translate geometary string to point array
1368 if (preg_match('#POLYGON\\(\\(([- 0-9.,]+)#',$aPointPolygon['astext'],$aMatch))
1370 preg_match_all('/(-?[0-9.]+) (-?[0-9.]+)/',$aMatch[1],$aPolyPoints,PREG_SET_ORDER);
1372 elseif (preg_match('#MULTIPOLYGON\\(\\(\\(([- 0-9.,]+)#',$aPointPolygon['astext'],$aMatch))
1374 preg_match_all('/(-?[0-9.]+) (-?[0-9.]+)/',$aMatch[1],$aPolyPoints,PREG_SET_ORDER);
1376 elseif (preg_match('#POINT\\((-?[0-9.]+) (-?[0-9.]+)\\)#',$aPointPolygon['astext'],$aMatch))
1379 $iSteps = ($fRadius * 40000)^2;
1380 $fStepSize = (2*pi())/$iSteps;
1381 $aPolyPoints = array();
1382 for($f = 0; $f < 2*pi(); $f += $fStepSize)
1384 $aPolyPoints[] = array('',$aMatch[1]+($fRadius*sin($f)),$aMatch[2]+($fRadius*cos($f)));
1386 $aPointPolygon['minlat'] = $aPointPolygon['minlat'] - $fRadius;
1387 $aPointPolygon['maxlat'] = $aPointPolygon['maxlat'] + $fRadius;
1388 $aPointPolygon['minlon'] = $aPointPolygon['minlon'] - $fRadius;
1389 $aPointPolygon['maxlon'] = $aPointPolygon['maxlon'] + $fRadius;
1393 // Output data suitable for display (points and a bounding box)
1394 if ($bShowPolygons && isset($aPolyPoints))
1396 $aResult['aPolyPoints'] = array();
1397 foreach($aPolyPoints as $aPoint)
1399 $aResult['aPolyPoints'][] = array($aPoint[1], $aPoint[2]);
1402 $aResult['aBoundingBox'] = array($aPointPolygon['minlat'],$aPointPolygon['maxlat'],$aPointPolygon['minlon'],$aPointPolygon['maxlon']);
1406 if ($aResult['extra_place'] == 'city')
1408 $aResult['class'] = 'place';
1409 $aResult['type'] = 'city';
1410 $aResult['rank_search'] = 16;
1413 if (!isset($aResult['aBoundingBox']))
1416 $fDiameter = 0.0001;
1418 if (isset($aClassType[$aResult['class'].':'.$aResult['type'].':'.$aResult['admin_level']]['defdiameter'])
1419 && $aClassType[$aResult['class'].':'.$aResult['type'].':'.$aResult['admin_level']]['defdiameter'])
1421 $fDiameter = $aClassType[$aResult['class'].':'.$aResult['type'].':'.$aResult['admin_level']]['defzoom'];
1423 elseif (isset($aClassType[$aResult['class'].':'.$aResult['type']]['defdiameter'])
1424 && $aClassType[$aResult['class'].':'.$aResult['type']]['defdiameter'])
1426 $fDiameter = $aClassType[$aResult['class'].':'.$aResult['type']]['defdiameter'];
1428 $fRadius = $fDiameter / 2;
1430 $iSteps = max(8,min(100,$fRadius * 3.14 * 100000));
1431 $fStepSize = (2*pi())/$iSteps;
1432 $aPolyPoints = array();
1433 for($f = 0; $f < 2*pi(); $f += $fStepSize)
1435 $aPolyPoints[] = array('',$aResult['lon']+($fRadius*sin($f)),$aResult['lat']+($fRadius*cos($f)));
1437 $aPointPolygon['minlat'] = $aResult['lat'] - $fRadius;
1438 $aPointPolygon['maxlat'] = $aResult['lat'] + $fRadius;
1439 $aPointPolygon['minlon'] = $aResult['lon'] - $fRadius;
1440 $aPointPolygon['maxlon'] = $aResult['lon'] + $fRadius;
1442 // Output data suitable for display (points and a bounding box)
1445 $aResult['aPolyPoints'] = array();
1446 foreach($aPolyPoints as $aPoint)
1448 $aResult['aPolyPoints'][] = array($aPoint[1], $aPoint[2]);
1451 $aResult['aBoundingBox'] = array($aPointPolygon['minlat'],$aPointPolygon['maxlat'],$aPointPolygon['minlon'],$aPointPolygon['maxlon']);
1454 // Is there an icon set for this type of result?
1455 if (isset($aClassType[$aResult['class'].':'.$aResult['type']]['icon'])
1456 && $aClassType[$aResult['class'].':'.$aResult['type']]['icon'])
1458 $aResult['icon'] = CONST_Website_BaseURL.'images/mapicons/'.$aClassType[$aResult['class'].':'.$aResult['type']]['icon'].'.p.20.png';
1461 if (isset($aClassType[$aResult['class'].':'.$aResult['type']]['label'])
1462 && $aClassType[$aResult['class'].':'.$aResult['type']]['label'])
1464 $aResult['label'] = $aClassType[$aResult['class'].':'.$aResult['type']]['label'];
1467 if ($bShowAddressDetails)
1469 $aResult['address'] = getAddressDetails($oDB, $sLanguagePrefArraySQL, $aResult['place_id'], $aResult['country_code']);
1470 if ($aResult['extra_place'] == 'city' && !isset($aResult['address']['city']))
1472 $aResult['address'] = array_merge(array('city' => array_shift(array_values($aResult['address']))), $aResult['address']);
1475 //var_dump($aResult['address']);
1479 // Adjust importance for the number of exact string matches in the result
1480 $aResult['importance'] = max(0.001,$aResult['importance']);
1482 $sAddress = $aResult['langaddress'];
1483 foreach($aRecheckWords as $i => $sWord)
1485 if (stripos($sAddress, $sWord)!==false) $iCountWords++;
1488 $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
1490 //if (CONST_Debug) var_dump($aResult['class'].':'.$aResult['type'].':'.$aResult['admin_level']);
1492 if (isset($aClassType[$aResult['class'].':'.$aResult['type'].':'.$aResult['admin_level']]['importance'])
1493 && $aClassType[$aResult['class'].':'.$aResult['type'].':'.$aResult['admin_level']]['importance'])
1495 $aResult['importance'] = $aClassType[$aResult['class'].':'.$aResult['type'].':'.$aResult['admin_level']]['importance'];
1497 elseif (isset($aClassType[$aResult['class'].':'.$aResult['type']]['importance'])
1498 && $aClassType[$aResult['class'].':'.$aResult['type']]['importance'])
1500 $aResult['importance'] = $aClassType[$aResult['class'].':'.$aResult['type']]['importance'];
1504 $aResult['importance'] = 1000000000000000;
1507 $aResult['name'] = $aResult['langaddress'];
1508 $aResult['foundorder'] = -$aResult['addressimportance'];
1509 $aSearchResults[$iResNum] = $aResult;
1511 uasort($aSearchResults, 'byImportance');
1513 $aOSMIDDone = array();
1514 $aClassTypeNameDone = array();
1515 $aToFilter = $aSearchResults;
1516 $aSearchResults = array();
1519 foreach($aToFilter as $iResNum => $aResult)
1521 if ($aResult['type'] == 'adminitrative') $aResult['type'] = 'administrative';
1522 $aExcludePlaceIDs[$aResult['place_id']] = $aResult['place_id'];
1525 $fLat = $aResult['lat'];
1526 $fLon = $aResult['lon'];
1527 if (isset($aResult['zoom'])) $iZoom = $aResult['zoom'];
1530 if (!$bDeDupe || (!isset($aOSMIDDone[$aResult['osm_type'].$aResult['osm_id']])
1531 && !isset($aClassTypeNameDone[$aResult['osm_type'].$aResult['class'].$aResult['type'].$aResult['name'].$aResult['admin_level']])))
1533 $aOSMIDDone[$aResult['osm_type'].$aResult['osm_id']] = true;
1534 $aClassTypeNameDone[$aResult['osm_type'].$aResult['class'].$aResult['type'].$aResult['name'].$aResult['admin_level']] = true;
1535 $aSearchResults[] = $aResult;
1538 // Absolute limit on number of results
1539 if (sizeof($aSearchResults) >= $iFinalLimit) break;
1542 $sDataDate = $oDB->getOne("select TO_CHAR(lastimportdate - '2 minutes'::interval,'YYYY/MM/DD HH24:MI')||' GMT' from import_status limit 1");
1544 if (isset($_GET['nearlat']) && isset($_GET['nearlon']))
1546 $sQuery .= ' ['.$_GET['nearlat'].','.$_GET['nearlon'].']';
1551 logEnd($oDB, $hLog, sizeof($aToFilter));
1553 $sMoreURL = CONST_Website_BaseURL.'search?format='.urlencode($sOutputFormat).'&exclude_place_ids='.join(',',$aExcludePlaceIDs);
1554 if (isset($_SERVER["HTTP_ACCEPT_LANGUAGE"])) $sMoreURL .= '&accept-language='.$_SERVER["HTTP_ACCEPT_LANGUAGE"];
1555 if ($bShowPolygons) $sMoreURL .= '&polygon=1';
1556 if ($bShowAddressDetails) $sMoreURL .= '&addressdetails=1';
1557 if (isset($_GET['viewbox']) && $_GET['viewbox']) $sMoreURL .= '&viewbox='.urlencode($_GET['viewbox']);
1558 if (isset($_GET['nearlat']) && isset($_GET['nearlon'])) $sMoreURL .= '&nearlat='.(float)$_GET['nearlat'].'&nearlon='.(float)$_GET['nearlon'];
1559 $sMoreURL .= '&q='.urlencode($sQuery);
1561 if (CONST_Debug) exit;
1563 include(CONST_BasePath.'/lib/template/search-'.$sOutputFormat.'.php');