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;
81 if (isset($aLangPrefOrder['name:pl'])) $bReverseInPlan = true;
83 $sLanguagePrefArraySQL = "ARRAY[".join(',',array_map("getDBQuoted",$aLangPrefOrder))."]";
85 if (isset($_GET['exclude_place_ids']) && $_GET['exclude_place_ids'])
87 foreach(explode(',',$_GET['exclude_place_ids']) as $iExcludedPlaceID)
89 $iExcludedPlaceID = (int)$iExcludedPlaceID;
90 if ($iExcludedPlaceID) $aExcludePlaceIDs[$iExcludedPlaceID] = $iExcludedPlaceID;
94 // Only certain ranks of feature
95 if (isset($_GET['featureType']) && !isset($_GET['featuretype'])) $_GET['featuretype'] = $_GET['featureType'];
97 if (isset($_GET['featuretype']))
99 switch($_GET['featuretype'])
102 $iMinAddressRank = $iMaxAddressRank = 4;
105 $iMinAddressRank = $iMaxAddressRank = 8;
108 $iMinAddressRank = 14;
109 $iMaxAddressRank = 16;
112 $iMinAddressRank = 8;
113 $iMaxAddressRank = 20;
118 if (isset($_GET['countrycodes']))
120 $aCountryCodes = array();
121 foreach(explode(',',$_GET['countrycodes']) as $sCountryCode)
123 if (preg_match('/^[a-zA-Z][a-zA-Z]$/', $sCountryCode))
125 $aCountryCodes[] = "'".strtolower($sCountryCode)."'";
128 $sCountryCodesSQL = join(',', $aCountryCodes);
132 $sQuery = (isset($_GET['q'])?trim($_GET['q']):'');
133 if (!$sQuery && isset($_SERVER['PATH_INFO']) && $_SERVER['PATH_INFO'][0] == '/')
135 $sQuery = substr($_SERVER['PATH_INFO'], 1);
137 // reverse order of '/' separated string
138 $aPhrases = explode('/', $sQuery);
139 $aPhrases = array_reverse($aPhrases);
140 $sQuery = join(', ',$aPhrases);
144 $aStructuredOptions = array(
145 array('amenity', 26, 30, false),
146 array('street', 26, 30, false),
147 array('city', 14, 24, false),
148 array('county', 9, 13, false),
149 array('state', 8, 8, false),
150 array('country', 4, 4, false),
151 array('postalcode', 5, 11, array(5, 11)),
153 $aStructuredQuery = array();
154 $sAllowedTypesSQLList = '';
155 foreach($aStructuredOptions as $aStructuredOption)
157 loadStructuredAddressElement($aStructuredQuery, $iMinAddressRank, $iMaxAddressRank, $aAddressRankList, $_GET, $aStructuredOption[0], $aStructuredOption[1], $aStructuredOption[2], $aStructuredOption[3]);
159 if (sizeof($aStructuredQuery) > 0)
161 $sQuery = join(', ', $aStructuredQuery);
162 if ($iMaxAddressRank < 30)
164 $sAllowedTypesSQLList = '(\'place\',\'boundary\')';
170 $hLog = logStart($oDB, 'search', $sQuery, $aLangPrefOrder);
172 // Hack to make it handle "new york, ny" (and variants) correctly
173 $sQuery = str_ireplace(array('New York, ny','new york, new york', 'New York ny','new york new york'), 'new york city, ny', $sQuery);
174 if (isset($aLangPrefOrder['name:en']))
176 $sQuery = preg_replace('/,\s*il\s*(,|$)/',', illinois\1', $sQuery);
177 $sQuery = preg_replace('/,\s*al\s*(,|$)/',', alabama\1', $sQuery);
178 $sQuery = preg_replace('/,\s*la\s*(,|$)/',', louisiana\1', $sQuery);
181 // If we have a view box create the SQL
182 // Small is the actual view box, Large is double (on each axis) that
183 $sViewboxCentreSQL = $sViewboxSmallSQL = $sViewboxLargeSQL = false;
184 if (isset($_GET['viewboxlbrt']) && $_GET['viewboxlbrt'])
186 $aCoOrdinatesLBRT = explode(',',$_GET['viewboxlbrt']);
187 $_GET['viewbox'] = $aCoOrdinatesLBRT[0].','.$aCoOrdinatesLBRT[3].','.$aCoOrdinatesLBRT[2].','.$aCoOrdinatesLBRT[1];
189 if (isset($_GET['viewbox']) && $_GET['viewbox'])
191 $aCoOrdinates = explode(',',$_GET['viewbox']);
192 $sViewboxSmallSQL = "ST_SetSRID(ST_MakeBox2D(ST_Point(".(float)$aCoOrdinates[0].",".(float)$aCoOrdinates[1]."),ST_Point(".(float)$aCoOrdinates[2].",".(float)$aCoOrdinates[3].")),4326)";
193 $fHeight = $aCoOrdinates[0]-$aCoOrdinates[2];
194 $fWidth = $aCoOrdinates[1]-$aCoOrdinates[3];
195 $aCoOrdinates[0] += $fHeight;
196 $aCoOrdinates[2] -= $fHeight;
197 $aCoOrdinates[1] += $fWidth;
198 $aCoOrdinates[3] -= $fWidth;
199 $sViewboxLargeSQL = "ST_SetSRID(ST_MakeBox2D(ST_Point(".(float)$aCoOrdinates[0].",".(float)$aCoOrdinates[1]."),ST_Point(".(float)$aCoOrdinates[2].",".(float)$aCoOrdinates[3].")),4326)";
203 $bBoundingBoxSearch = false;
205 if (isset($_GET['route']) && $_GET['route'] && isset($_GET['routewidth']) && $_GET['routewidth'])
207 $aPoints = explode(',',$_GET['route']);
208 if (sizeof($aPoints) % 2 != 0)
210 userError("Uneven number of points");
213 $sViewboxCentreSQL = "ST_SetSRID('LINESTRING(";
215 foreach($aPoints as $i => $fPoint)
219 if ($i != 1) $sViewboxCentreSQL .= ",";
220 $sViewboxCentreSQL .= ((float)$fPoint).' '.$fPrevCoord;
224 $fPrevCoord = (float)$fPoint;
227 $sViewboxCentreSQL .= ")'::geometry,4326)";
229 $sSQL = "select st_buffer(".$sViewboxCentreSQL.",".(float)($_GET['routewidth']/69).")";
230 $sViewboxSmallSQL = $oDB->getOne($sSQL);
231 if (PEAR::isError($sViewboxSmallSQL))
233 failInternalError("Could not get small viewbox.", $sSQL, $sViewboxSmallSQL);
235 $sViewboxSmallSQL = "'".$sViewboxSmallSQL."'::geometry";
237 $sSQL = "select st_buffer(".$sViewboxCentreSQL.",".(float)($_GET['routewidth']/30).")";
238 $sViewboxLargeSQL = $oDB->getOne($sSQL);
239 if (PEAR::isError($sViewboxLargeSQL))
241 failInternalError("Could not get large viewbox.", $sSQL, $sViewboxLargeSQL);
243 $sViewboxLargeSQL = "'".$sViewboxLargeSQL."'::geometry";
244 $bBoundingBoxSearch = true;
247 // Do we have anything that looks like a lat/lon pair?
248 if (preg_match('/\\b([NS])[ ]+([0-9]+[0-9.]*)[ ]+([0-9.]+)?[, ]+([EW])[ ]+([0-9]+)[ ]+([0-9]+[0-9.]*)?\\b/', $sQuery, $aData))
250 $fQueryLat = ($aData[1]=='N'?1:-1) * ($aData[2] + $aData[3]/60);
251 $fQueryLon = ($aData[4]=='E'?1:-1) * ($aData[5] + $aData[6]/60);
252 if ($fQueryLat <= 90.1 && $fQueryLat >= -90.1 && $fQueryLon <= 180.1 && $fQueryLon >= -180.1)
254 $_GET['nearlat'] = $fQueryLat;
255 $_GET['nearlon'] = $fQueryLon;
256 $sQuery = trim(str_replace($aData[0], ' ', $sQuery));
259 elseif (preg_match('/\\b([0-9]+)[ ]+([0-9]+[0-9.]*)?[ ]+([NS])[, ]+([0-9]+)[ ]+([0-9]+[0-9.]*)?[ ]+([EW])\\b/', $sQuery, $aData))
261 $fQueryLat = ($aData[3]=='N'?1:-1) * ($aData[1] + $aData[2]/60);
262 $fQueryLon = ($aData[6]=='E'?1:-1) * ($aData[4] + $aData[5]/60);
263 if ($fQueryLat <= 90.1 && $fQueryLat >= -90.1 && $fQueryLon <= 180.1 && $fQueryLon >= -180.1)
265 $_GET['nearlat'] = $fQueryLat;
266 $_GET['nearlon'] = $fQueryLon;
267 $sQuery = trim(str_replace($aData[0], ' ', $sQuery));
270 elseif (preg_match('/(\\[|^|\\b)(-?[0-9]+[0-9.]*)[, ]+(-?[0-9]+[0-9.]*)(\\]|$|\\b)/', $sQuery, $aData))
272 $fQueryLat = $aData[2];
273 $fQueryLon = $aData[3];
274 if ($fQueryLat <= 90.1 && $fQueryLat >= -90.1 && $fQueryLon <= 180.1 && $fQueryLon >= -180.1)
276 $_GET['nearlat'] = $fQueryLat;
277 $_GET['nearlon'] = $fQueryLon;
278 $sQuery = trim(str_replace($aData[0], ' ', $sQuery));
282 if ($sQuery || $aStructuredQuery)
284 // Start with a blank search
286 array('iSearchRank' => 0, 'iNamePhrase' => -1, 'sCountryCode' => false, 'aName'=>array(), 'aAddress'=>array(), 'aFullNameAddress'=>array(),
287 'aNameNonSearch'=>array(), 'aAddressNonSearch'=>array(),
288 'sOperator'=>'', 'aFeatureName' => array(), 'sClass'=>'', 'sType'=>'', 'sHouseNumber'=>'', 'fLat'=>'', 'fLon'=>'', 'fRadius'=>'')
291 $sNearPointSQL = false;
292 if (isset($_GET['nearlat']) && isset($_GET['nearlon']))
294 $sNearPointSQL = "ST_SetSRID(ST_Point(".(float)$_GET['nearlon'].",".(float)$_GET['nearlat']."),4326)";
295 $aSearches[0]['fLat'] = (float)$_GET['nearlat'];
296 $aSearches[0]['fLon'] = (float)$_GET['nearlon'];
297 $aSearches[0]['fRadius'] = 0.1;
300 $bSpecialTerms = false;
301 preg_match_all('/\\[(.*)=(.*)\\]/', $sQuery, $aSpecialTermsRaw, PREG_SET_ORDER);
302 $aSpecialTerms = array();
303 foreach($aSpecialTermsRaw as $aSpecialTerm)
305 $sQuery = str_replace($aSpecialTerm[0], ' ', $sQuery);
306 $aSpecialTerms[strtolower($aSpecialTerm[1])] = $aSpecialTerm[2];
309 preg_match_all('/\\[([\\w ]*)\\]/u', $sQuery, $aSpecialTermsRaw, PREG_SET_ORDER);
310 $aSpecialTerms = array();
311 if (isset($aStructuredQuery['amenity']) && $aStructuredQuery['amenity'])
313 $aSpecialTermsRaw[] = array('['.$aStructuredQuery['amenity'].']', $aStructuredQuery['amenity']);
314 unset($aStructuredQuery['amenity']);
316 foreach($aSpecialTermsRaw as $aSpecialTerm)
318 $sQuery = str_replace($aSpecialTerm[0], ' ', $sQuery);
319 $sToken = $oDB->getOne("select make_standard_name('".$aSpecialTerm[1]."') as string");
320 $sSQL = 'select * from (select word_id,word_token, word, class, type, country_code, operator';
321 $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';
322 if (CONST_Debug) var_Dump($sSQL);
323 $aSearchWords = $oDB->getAll($sSQL);
324 $aNewSearches = array();
325 foreach($aSearches as $aSearch)
327 foreach($aSearchWords as $aSearchTerm)
329 $aNewSearch = $aSearch;
330 if ($aSearchTerm['country_code'])
332 $aNewSearch['sCountryCode'] = strtolower($aSearchTerm['country_code']);
333 $aNewSearches[] = $aNewSearch;
334 $bSpecialTerms = true;
336 if ($aSearchTerm['class'])
338 $aNewSearch['sClass'] = $aSearchTerm['class'];
339 $aNewSearch['sType'] = $aSearchTerm['type'];
340 $aNewSearches[] = $aNewSearch;
341 $bSpecialTerms = true;
345 $aSearches = $aNewSearches;
348 // Split query into phrases
349 // Commas are used to reduce the search space by indicating where phrases split
350 if (sizeof($aStructuredQuery) > 0)
352 $aPhrases = $aStructuredQuery;
353 $bStructuredPhrases = true;
357 $aPhrases = explode(',',$sQuery);
358 $bStructuredPhrases = false;
361 // Convert each phrase to standard form
362 // Create a list of standard words
363 // Get all 'sets' of words
364 // Generate a complete list of all
366 foreach($aPhrases as $iPhrase => $sPhrase)
368 $aPhrase = $oDB->getRow("select make_standard_name('".pg_escape_string($sPhrase)."') as string");
369 if (PEAR::isError($aPhrase))
371 userError("Illegal query string (not an UTF-8 string): ".$sPhrase);
372 if (CONST_Debug) var_dump($aPhrase);
375 if (trim($aPhrase['string']))
377 $aPhrases[$iPhrase] = $aPhrase;
378 $aPhrases[$iPhrase]['words'] = explode(' ',$aPhrases[$iPhrase]['string']);
379 $aPhrases[$iPhrase]['wordsets'] = getWordSets($aPhrases[$iPhrase]['words'], 0);
380 $aTokens = array_merge($aTokens, getTokensFromSets($aPhrases[$iPhrase]['wordsets']));
384 unset($aPhrases[$iPhrase]);
388 // reindex phrases - we make assumptions later on
389 $aPhraseTypes = array_keys($aPhrases);
390 $aPhrases = array_values($aPhrases);
392 if (sizeof($aTokens))
395 // Check which tokens we have, get the ID numbers
396 $sSQL = 'select word_id,word_token, word, class, type, country_code, operator, search_name_count';
397 $sSQL .= ' from word where word_token in ('.join(',',array_map("getDBQuoted",$aTokens)).')';
398 //$sSQL .= ' and search_name_count < '.CONST_Max_Word_Frequency;
399 //$sSQL .= ' group by word_token, word, class, type, country_code';
401 if (CONST_Debug) var_Dump($sSQL);
403 $aValidTokens = array();
404 if (sizeof($aTokens)) $aDatabaseWords = $oDB->getAll($sSQL);
405 else $aDatabaseWords = array();
406 if (PEAR::IsError($aDatabaseWords))
408 failInternalError("Could not get word tokens.", $sSQL, $aDatabaseWords);
410 $aPossibleMainWordIDs = array();
411 $aWordFrequencyScores = array();
412 foreach($aDatabaseWords as $aToken)
414 // Very special case - require 2 letter country param to match the country code found
415 if ($bStructuredPhrases && $aToken['country_code'] && !empty($aStructuredQuery['country'])
416 && strlen($aStructuredQuery['country']) == 2 && strtolower($aStructuredQuery['country']) != $aToken['country_code'])
421 if (isset($aValidTokens[$aToken['word_token']]))
423 $aValidTokens[$aToken['word_token']][] = $aToken;
427 $aValidTokens[$aToken['word_token']] = array($aToken);
429 if (!$aToken['class'] && !$aToken['country_code']) $aPossibleMainWordIDs[$aToken['word_id']] = 1;
430 $aWordFrequencyScores[$aToken['word_id']] = $aToken['search_name_count'] + 1;
432 if (CONST_Debug) var_Dump($aPhrases, $aValidTokens);
434 // Try and calculate GB postcodes we might be missing
435 foreach($aTokens as $sToken)
437 // Source of gb postcodes is now definitive - always use
438 if (preg_match('/^([A-Z][A-Z]?[0-9][0-9A-Z]? ?[0-9])([A-Z][A-Z])$/', strtoupper(trim($sToken)), $aData))
440 if (substr($aData[1],-2,1) != ' ')
442 $aData[0] = substr($aData[0],0,strlen($aData[1]-1)).' '.substr($aData[0],strlen($aData[1]-1));
443 $aData[1] = substr($aData[1],0,-1).' '.substr($aData[1],-1,1);
445 $aGBPostcodeLocation = gbPostcodeCalculate($aData[0], $aData[1], $aData[2], $oDB);
446 if ($aGBPostcodeLocation)
448 $aValidTokens[$sToken] = $aGBPostcodeLocation;
453 foreach($aTokens as $sToken)
455 // Unknown single word token with a number - assume it is a house number
456 if (!isset($aValidTokens[' '.$sToken]) && strpos($sToken,' ') === false && preg_match('/[0-9]/', $sToken))
458 $aValidTokens[' '.$sToken] = array(array('class'=>'place','type'=>'house'));
462 // Any words that have failed completely?
465 // Start the search process
466 $aResultPlaceIDs = array();
469 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 (isset($aSearchTerm['word_id']) && $aSearchTerm['word_id'])
538 // If we already have a name try putting the postcode first
539 if (sizeof($aSearch['aName']))
541 $aNewSearch = $aSearch;
542 $aNewSearch['aAddress'] = array_merge($aNewSearch['aAddress'], $aNewSearch['aName']);
543 $aNewSearch['aName'] = array();
544 $aNewSearch['aName'][$aSearchTerm['word_id']] = $aSearchTerm['word_id'];
545 if ($aSearch['iSearchRank'] < $iMaxRank) $aNewWordsetSearches[] = $aNewSearch;
548 if (sizeof($aSearch['aName']))
550 if ((!$bStructuredPhrases || $iPhrase > 0) && $sPhraseType != 'country' && (!isset($aValidTokens[$sToken]) || strlen($sToken) < 4 || strpos($sToken, ' ') !== false))
552 $aSearch['aAddress'][$aSearchTerm['word_id']] = $aSearchTerm['word_id'];
556 $aCurrentSearch['aFullNameAddress'][$aSearchTerm['word_id']] = $aSearchTerm['word_id'];
557 $aSearch['iSearchRank'] += 1000; // skip;
562 $aSearch['aName'][$aSearchTerm['word_id']] = $aSearchTerm['word_id'];
563 //$aSearch['iNamePhrase'] = $iPhrase;
565 if ($aSearch['iSearchRank'] < $iMaxRank) $aNewWordsetSearches[] = $aSearch;
569 elseif (($sPhraseType == '' || $sPhraseType == 'street') && $aSearchTerm['class'] == 'place' && $aSearchTerm['type'] == 'house')
571 if ($aSearch['sHouseNumber'] === '')
573 $aSearch['sHouseNumber'] = $sToken;
574 if ($aSearch['iSearchRank'] < $iMaxRank) $aNewWordsetSearches[] = $aSearch;
576 // Fall back to not searching for this item (better than nothing)
577 $aSearch = $aCurrentSearch;
578 $aSearch['iSearchRank'] += 1;
579 if ($aSearch['iSearchRank'] < $iMaxRank) $aNewWordsetSearches[] = $aSearch;
583 elseif ($sPhraseType == '' && $aSearchTerm['class'] !== '' && $aSearchTerm['class'] !== null)
585 if ($aSearch['sClass'] === '')
587 $aSearch['sOperator'] = $aSearchTerm['operator'];
588 $aSearch['sClass'] = $aSearchTerm['class'];
589 $aSearch['sType'] = $aSearchTerm['type'];
590 if (sizeof($aSearch['aName'])) $aSearch['sOperator'] = 'name';
591 else $aSearch['sOperator'] = 'near'; // near = in for the moment
593 // Do we have a shortcut id?
594 if ($aSearch['sOperator'] == 'name')
596 $sSQL = "select get_tagpair('".$aSearch['sClass']."', '".$aSearch['sType']."')";
597 if ($iAmenityID = $oDB->getOne($sSQL))
599 $aValidTokens[$aSearch['sClass'].':'.$aSearch['sType']] = array('word_id' => $iAmenityID);
600 $aSearch['aName'][$iAmenityID] = $iAmenityID;
601 $aSearch['sClass'] = '';
602 $aSearch['sType'] = '';
605 if ($aSearch['iSearchRank'] < $iMaxRank) $aNewWordsetSearches[] = $aSearch;
608 elseif (isset($aSearchTerm['word_id']) && $aSearchTerm['word_id'])
610 if (sizeof($aSearch['aName']))
612 if ((!$bStructuredPhrases || $iPhrase > 0) && $sPhraseType != 'country' && (!isset($aValidTokens[$sToken]) || strlen($sToken) < 4 || strpos($sToken, ' ') !== false))
614 $aSearch['aAddress'][$aSearchTerm['word_id']] = $aSearchTerm['word_id'];
618 $aCurrentSearch['aFullNameAddress'][$aSearchTerm['word_id']] = $aSearchTerm['word_id'];
619 $aSearch['iSearchRank'] += 1000; // skip;
624 $aSearch['aName'][$aSearchTerm['word_id']] = $aSearchTerm['word_id'];
625 //$aSearch['iNamePhrase'] = $iPhrase;
627 if ($aSearch['iSearchRank'] < $iMaxRank) $aNewWordsetSearches[] = $aSearch;
631 if (isset($aValidTokens[$sToken]))
633 // Allow searching for a word - but at extra cost
634 foreach($aValidTokens[$sToken] as $aSearchTerm)
636 if (isset($aSearchTerm['word_id']) && $aSearchTerm['word_id'])
638 if ((!$bStructuredPhrases || $iPhrase > 0) && sizeof($aCurrentSearch['aName']) && strlen($sToken) >= 4)
640 $aSearch = $aCurrentSearch;
641 $aSearch['iSearchRank'] += 1;
642 if ($aWordFrequencyScores[$aSearchTerm['word_id']] < CONST_Max_Word_Frequency)
644 $aSearch['aAddress'][$aSearchTerm['word_id']] = $aSearchTerm['word_id'];
645 if ($aSearch['iSearchRank'] < $iMaxRank) $aNewWordsetSearches[] = $aSearch;
647 elseif (isset($aValidTokens[' '.$sToken])) // revert to the token version?
649 foreach($aValidTokens[' '.$sToken] as $aSearchTermToken)
651 if (empty($aSearchTermToken['country_code'])
652 && empty($aSearchTermToken['lat'])
653 && empty($aSearchTermToken['class']))
655 $aSearch = $aCurrentSearch;
656 $aSearch['iSearchRank'] += 1;
657 $aSearch['aAddress'][$aSearchTermToken['word_id']] = $aSearchTermToken['word_id'];
658 if ($aSearch['iSearchRank'] < $iMaxRank) $aNewWordsetSearches[] = $aSearch;
664 $aSearch['aAddressNonSearch'][$aSearchTerm['word_id']] = $aSearchTerm['word_id'];
665 if ($aSearch['iSearchRank'] < $iMaxRank) $aNewWordsetSearches[] = $aSearch;
669 if (!sizeof($aCurrentSearch['aName']) || $aCurrentSearch['iNamePhrase'] == $iPhrase)
671 $aSearch = $aCurrentSearch;
672 $aSearch['iSearchRank'] += 2;
673 if (preg_match('#^[0-9]+$#', $sToken)) $aSearch['iSearchRank'] += 2;
674 if ($aWordFrequencyScores[$aSearchTerm['word_id']] < CONST_Max_Word_Frequency)
675 $aSearch['aName'][$aSearchTerm['word_id']] = $aSearchTerm['word_id'];
677 $aSearch['aNameNonSearch'][$aSearchTerm['word_id']] = $aSearchTerm['word_id'];
678 $aSearch['iNamePhrase'] = $iPhrase;
679 if ($aSearch['iSearchRank'] < $iMaxRank) $aNewWordsetSearches[] = $aSearch;
686 // Allow skipping a word - but at EXTREAM cost
687 //$aSearch = $aCurrentSearch;
688 //$aSearch['iSearchRank']+=100;
689 //$aNewWordsetSearches[] = $aSearch;
693 usort($aNewWordsetSearches, 'bySearchRank');
694 $aWordsetSearches = array_slice($aNewWordsetSearches, 0, 50);
696 //var_Dump('<hr>',sizeof($aWordsetSearches)); exit;
698 $aNewPhraseSearches = array_merge($aNewPhraseSearches, $aNewWordsetSearches);
699 usort($aNewPhraseSearches, 'bySearchRank');
701 $aSearchHash = array();
702 foreach($aNewPhraseSearches as $iSearch => $aSearch)
704 $sHash = serialize($aSearch);
705 if (isset($aSearchHash[$sHash])) unset($aNewPhraseSearches[$iSearch]);
706 else $aSearchHash[$sHash] = 1;
709 $aNewPhraseSearches = array_slice($aNewPhraseSearches, 0, 50);
712 // Re-group the searches by their score, junk anything over 20 as just not worth trying
713 $aGroupedSearches = array();
714 foreach($aNewPhraseSearches as $aSearch)
716 if ($aSearch['iSearchRank'] < $iMaxRank)
718 if (!isset($aGroupedSearches[$aSearch['iSearchRank']])) $aGroupedSearches[$aSearch['iSearchRank']] = array();
719 $aGroupedSearches[$aSearch['iSearchRank']][] = $aSearch;
722 ksort($aGroupedSearches);
725 $aSearches = array();
726 foreach($aGroupedSearches as $iScore => $aNewSearches)
728 $iSearchCount += sizeof($aNewSearches);
729 $aSearches = array_merge($aSearches, $aNewSearches);
730 if ($iSearchCount > 50) break;
733 //if (CONST_Debug) _debugDumpGroupedSearches($aGroupedSearches, $aValidTokens);
740 // Re-group the searches by their score, junk anything over 20 as just not worth trying
741 $aGroupedSearches = array();
742 foreach($aSearches as $aSearch)
744 if ($aSearch['iSearchRank'] < $iMaxRank)
746 if (!isset($aGroupedSearches[$aSearch['iSearchRank']])) $aGroupedSearches[$aSearch['iSearchRank']] = array();
747 $aGroupedSearches[$aSearch['iSearchRank']][] = $aSearch;
750 ksort($aGroupedSearches);
753 if (CONST_Debug) var_Dump($aGroupedSearches);
757 $aCopyGroupedSearches = $aGroupedSearches;
758 foreach($aCopyGroupedSearches as $iGroup => $aSearches)
760 foreach($aSearches as $iSearch => $aSearch)
762 if (sizeof($aSearch['aAddress']))
764 $iReverseItem = array_pop($aSearch['aAddress']);
765 if (isset($aPossibleMainWordIDs[$iReverseItem]))
767 $aSearch['aAddress'] = array_merge($aSearch['aAddress'], $aSearch['aName']);
768 $aSearch['aName'] = array($iReverseItem);
769 $aGroupedSearches[$iGroup][] = $aSearch;
771 //$aReverseSearch['aName'][$iReverseItem] = $iReverseItem;
772 //$aGroupedSearches[$iGroup][] = $aReverseSearch;
778 if (CONST_Search_TryDroppedAddressTerms && sizeof($aStructuredQuery) > 0)
780 $aCopyGroupedSearches = $aGroupedSearches;
781 foreach($aCopyGroupedSearches as $iGroup => $aSearches)
783 foreach($aSearches as $iSearch => $aSearch)
785 $aReductionsList = array($aSearch['aAddress']);
786 $iSearchRank = $aSearch['iSearchRank'];
787 while(sizeof($aReductionsList) > 0)
790 if ($iSearchRank > iMaxRank) break 3;
791 $aNewReductionsList = array();
792 foreach($aReductionsList as $aReductionsWordList)
794 for ($iReductionWord = 0; $iReductionWord < sizeof($aReductionsWordList); $iReductionWord++)
796 $aReductionsWordListResult = array_merge(array_slice($aReductionsWordList, 0, $iReductionWord), array_slice($aReductionsWordList, $iReductionWord+1));
797 $aReverseSearch = $aSearch;
798 $aSearch['aAddress'] = $aReductionsWordListResult;
799 $aSearch['iSearchRank'] = $iSearchRank;
800 $aGroupedSearches[$iSearchRank][] = $aReverseSearch;
801 if (sizeof($aReductionsWordListResult) > 0)
803 $aNewReductionsList[] = $aReductionsWordListResult;
807 $aReductionsList = $aNewReductionsList;
811 ksort($aGroupedSearches);
814 // Filter out duplicate searches
815 $aSearchHash = array();
816 foreach($aGroupedSearches as $iGroup => $aSearches)
818 foreach($aSearches as $iSearch => $aSearch)
820 $sHash = serialize($aSearch);
821 if (isset($aSearchHash[$sHash]))
823 unset($aGroupedSearches[$iGroup][$iSearch]);
824 if (sizeof($aGroupedSearches[$iGroup]) == 0) unset($aGroupedSearches[$iGroup]);
828 $aSearchHash[$sHash] = 1;
833 if (CONST_Debug) _debugDumpGroupedSearches($aGroupedSearches, $aValidTokens);
837 foreach($aGroupedSearches as $iGroupedRank => $aSearches)
840 foreach($aSearches as $aSearch)
844 if (CONST_Debug) { echo "<hr><b>Search Loop, group $iGroupLoop, loop $iQueryLoop</b>"; }
845 if (CONST_Debug) _debugDumpGroupedSearches(array($iGroupedRank => array($aSearch)), $aValidTokens);
848 // Must have a location term
849 if (!sizeof($aSearch['aName']) && !sizeof($aSearch['aAddress']) && !$aSearch['fLon'])
851 if ($aSearch['sCountryCode'] && !$aSearch['sClass'] && !$aSearch['sHouseNumber'])
853 if (4 >= $iMinAddressRank && 4 <= $iMaxAddressRank)
855 $sSQL = "select place_id from placex where calculated_country_code='".$aSearch['sCountryCode']."' and rank_search = 4";
856 if ($sCountryCodesSQL) $sSQL .= " and calculated_country_code in ($sCountryCodesSQL)";
857 $sSQL .= " order by st_area(geometry) desc limit 1";
858 if (CONST_Debug) var_dump($sSQL);
859 $aPlaceIDs = $oDB->getCol($sSQL);
864 if (!$bBoundingBoxSearch && !$aSearch['fLon']) continue;
865 if (!$aSearch['sClass']) continue;
866 $sSQL = "select count(*) from pg_tables where tablename = 'place_classtype_".$aSearch['sClass']."_".$aSearch['sType']."'";
867 if ($oDB->getOne($sSQL))
869 $sSQL = "select place_id from place_classtype_".$aSearch['sClass']."_".$aSearch['sType']." ct";
870 if ($sCountryCodesSQL) $sSQL .= " join placex using (place_id)";
871 $sSQL .= " where st_contains($sViewboxSmallSQL, ct.centroid)";
872 if ($sCountryCodesSQL) $sSQL .= " and calculated_country_code in ($sCountryCodesSQL)";
873 if (sizeof($aExcludePlaceIDs))
875 $sSQL .= " and place_id not in (".join(',',$aExcludePlaceIDs).")";
877 if ($sViewboxCentreSQL) $sSQL .= " order by st_distance($sViewboxCentreSQL, ct.centroid) asc";
878 $sSQL .= " limit $iLimit";
879 if (CONST_Debug) var_dump($sSQL);
880 $aPlaceIDs = $oDB->getCol($sSQL);
882 // If excluded place IDs are given, it is fair to assume that
883 // there have been results in the small box, so no further
884 // expansion in that case.
885 if (!sizeof($aPlaceIDs) && !sizeof($aExcludePlaceIDs))
887 $sSQL = "select place_id from place_classtype_".$aSearch['sClass']."_".$aSearch['sType']." ct";
888 if ($sCountryCodesSQL) $sSQL .= " join placex using (place_id)";
889 $sSQL .= " where st_contains($sViewboxLargeSQL, ct.centroid)";
890 if ($sCountryCodesSQL) $sSQL .= " and calculated_country_code in ($sCountryCodesSQL)";
891 if ($sViewboxCentreSQL) $sSQL .= " order by st_distance($sViewboxCentreSQL, ct.centroid) asc";
892 $sSQL .= " limit $iLimit";
893 if (CONST_Debug) var_dump($sSQL);
894 $aPlaceIDs = $oDB->getCol($sSQL);
899 $sSQL = "select place_id from placex where class='".$aSearch['sClass']."' and type='".$aSearch['sType']."'";
900 $sSQL .= " and st_contains($sViewboxSmallSQL, geometry) and linked_place_id is null";
901 if ($sCountryCodesSQL) $sSQL .= " and calculated_country_code in ($sCountryCodesSQL)";
902 if ($sViewboxCentreSQL) $sSQL .= " order by st_distance($sViewboxCentreSQL, centroid) asc";
903 $sSQL .= " limit $iLimit";
904 if (CONST_Debug) var_dump($sSQL);
905 $aPlaceIDs = $oDB->getCol($sSQL);
911 $aPlaceIDs = array();
913 // First we need a position, either aName or fLat or both
917 // TODO: filter out the pointless search terms (2 letter name tokens and less)
918 // they might be right - but they are just too darned expensive to run
919 if (sizeof($aSearch['aName'])) $aTerms[] = "name_vector @> ARRAY[".join($aSearch['aName'],",")."]";
920 if (sizeof($aSearch['aNameNonSearch'])) $aTerms[] = "array_cat(name_vector,ARRAY[]::integer[]) @> ARRAY[".join($aSearch['aNameNonSearch'],",")."]";
921 if (sizeof($aSearch['aAddress']) && $aSearch['aName'] != $aSearch['aAddress'])
923 // For infrequent name terms disable index usage for address
924 if (CONST_Search_NameOnlySearchFrequencyThreshold &&
925 sizeof($aSearch['aName']) == 1 &&
926 $aWordFrequencyScores[$aSearch['aName'][reset($aSearch['aName'])]] < CONST_Search_NameOnlySearchFrequencyThreshold)
928 $aTerms[] = "array_cat(nameaddress_vector,ARRAY[]::integer[]) @> ARRAY[".join(array_merge($aSearch['aAddress'],$aSearch['aAddressNonSearch']),",")."]";
932 $aTerms[] = "nameaddress_vector @> ARRAY[".join($aSearch['aAddress'],",")."]";
933 if (sizeof($aSearch['aAddressNonSearch'])) $aTerms[] = "array_cat(nameaddress_vector,ARRAY[]::integer[]) @> ARRAY[".join($aSearch['aAddressNonSearch'],",")."]";
936 if ($aSearch['sCountryCode']) $aTerms[] = "country_code = '".pg_escape_string($aSearch['sCountryCode'])."'";
937 if ($aSearch['sHouseNumber']) $aTerms[] = "address_rank between 16 and 27";
938 if ($aSearch['fLon'] && $aSearch['fLat'])
940 $aTerms[] = "ST_DWithin(centroid, ST_SetSRID(ST_Point(".$aSearch['fLon'].",".$aSearch['fLat']."),4326), ".$aSearch['fRadius'].")";
941 $aOrder[] = "ST_Distance(centroid, ST_SetSRID(ST_Point(".$aSearch['fLon'].",".$aSearch['fLat']."),4326)) ASC";
943 if (sizeof($aExcludePlaceIDs))
945 $aTerms[] = "place_id not in (".join(',',$aExcludePlaceIDs).")";
947 if ($sCountryCodesSQL)
949 $aTerms[] = "country_code in ($sCountryCodesSQL)";
952 if ($bBoundingBoxSearch) $aTerms[] = "centroid && $sViewboxSmallSQL";
953 if ($sNearPointSQL) $aOrder[] = "ST_Distance($sNearPointSQL, centroid) asc";
955 $sImportanceSQL = '(case when importance = 0 OR importance IS NULL then 0.75-(search_rank::float/40) else importance end)';
956 if ($sViewboxSmallSQL) $sImportanceSQL .= " * case when ST_Contains($sViewboxSmallSQL, centroid) THEN 1 ELSE 0.5 END";
957 if ($sViewboxLargeSQL) $sImportanceSQL .= " * case when ST_Contains($sViewboxLargeSQL, centroid) THEN 1 ELSE 0.5 END";
958 $aOrder[] = "$sImportanceSQL DESC";
959 if (sizeof($aSearch['aFullNameAddress']))
961 $aOrder[] = '(select count(*) from (select unnest(ARRAY['.join($aSearch['aFullNameAddress'],",").']) INTERSECT select unnest(nameaddress_vector))s) DESC';
966 $sSQL = "select place_id";
967 $sSQL .= " from search_name";
968 $sSQL .= " where ".join(' and ',$aTerms);
969 $sSQL .= " order by ".join(', ',$aOrder);
970 if ($aSearch['sHouseNumber'] || $aSearch['sClass'])
971 $sSQL .= " limit 50";
972 elseif (!sizeof($aSearch['aName']) && !sizeof($aSearch['aAddress']) && $aSearch['sClass'])
975 $sSQL .= " limit ".$iLimit;
977 if (CONST_Debug) { var_dump($sSQL); }
978 $aViewBoxPlaceIDs = $oDB->getAll($sSQL);
979 if (PEAR::IsError($aViewBoxPlaceIDs))
981 failInternalError("Could not get places for search terms.", $sSQL, $aViewBoxPlaceIDs);
983 //var_dump($aViewBoxPlaceIDs);
984 // Did we have an viewbox matches?
985 $aPlaceIDs = array();
986 $bViewBoxMatch = false;
987 foreach($aViewBoxPlaceIDs as $aViewBoxRow)
989 //if ($bViewBoxMatch == 1 && $aViewBoxRow['in_small'] == 'f') break;
990 //if ($bViewBoxMatch == 2 && $aViewBoxRow['in_large'] == 'f') break;
991 //if ($aViewBoxRow['in_small'] == 't') $bViewBoxMatch = 1;
992 //else if ($aViewBoxRow['in_large'] == 't') $bViewBoxMatch = 2;
993 $aPlaceIDs[] = $aViewBoxRow['place_id'];
996 //var_Dump($aPlaceIDs);
999 if ($aSearch['sHouseNumber'] && sizeof($aPlaceIDs))
1001 $aRoadPlaceIDs = $aPlaceIDs;
1002 $sPlaceIDs = join(',',$aPlaceIDs);
1004 // Now they are indexed look for a house attached to a street we found
1005 $sHouseNumberRegex = '\\\\m'.str_replace(' ','[-,/ ]',$aSearch['sHouseNumber']).'\\\\M';
1006 $sSQL = "select place_id from placex where parent_place_id in (".$sPlaceIDs.") and housenumber ~* E'".$sHouseNumberRegex."'";
1007 if (sizeof($aExcludePlaceIDs))
1009 $sSQL .= " and place_id not in (".join(',',$aExcludePlaceIDs).")";
1011 $sSQL .= " limit $iLimit";
1012 if (CONST_Debug) var_dump($sSQL);
1013 $aPlaceIDs = $oDB->getCol($sSQL);
1015 // If not try the aux fallback table
1017 if (!sizeof($aPlaceIDs))
1019 $sSQL = "select place_id from location_property_aux where parent_place_id in (".$sPlaceIDs.") and housenumber = '".pg_escape_string($aSearch['sHouseNumber'])."'";
1020 if (sizeof($aExcludePlaceIDs))
1022 $sSQL .= " and place_id not in (".join(',',$aExcludePlaceIDs).")";
1024 //$sSQL .= " limit $iLimit";
1025 if (CONST_Debug) var_dump($sSQL);
1026 $aPlaceIDs = $oDB->getCol($sSQL);
1030 if (!sizeof($aPlaceIDs))
1032 $sSQL = "select place_id from location_property_tiger where parent_place_id in (".$sPlaceIDs.") and housenumber = '".pg_escape_string($aSearch['sHouseNumber'])."'";
1033 if (sizeof($aExcludePlaceIDs))
1035 $sSQL .= " and place_id not in (".join(',',$aExcludePlaceIDs).")";
1037 //$sSQL .= " limit $iLimit";
1038 if (CONST_Debug) var_dump($sSQL);
1039 $aPlaceIDs = $oDB->getCol($sSQL);
1042 // Fallback to the road
1043 if (!sizeof($aPlaceIDs) && preg_match('/[0-9]+/', $aSearch['sHouseNumber']))
1045 $aPlaceIDs = $aRoadPlaceIDs;
1050 if ($aSearch['sClass'] && sizeof($aPlaceIDs))
1052 $sPlaceIDs = join(',',$aPlaceIDs);
1053 $aClassPlaceIDs = array();
1055 if (!$aSearch['sOperator'] || $aSearch['sOperator'] == 'name')
1057 // If they were searching for a named class (i.e. 'Kings Head pub') then we might have an extra match
1058 $sSQL = "select place_id from placex where place_id in ($sPlaceIDs) and class='".$aSearch['sClass']."' and type='".$aSearch['sType']."'";
1059 $sSQL .= " and linked_place_id is null";
1060 if ($sCountryCodesSQL) $sSQL .= " and calculated_country_code in ($sCountryCodesSQL)";
1061 $sSQL .= " order by rank_search asc limit $iLimit";
1062 if (CONST_Debug) var_dump($sSQL);
1063 $aClassPlaceIDs = $oDB->getCol($sSQL);
1066 if (!$aSearch['sOperator'] || $aSearch['sOperator'] == 'near') // & in
1068 $sSQL = "select count(*) from pg_tables where tablename = 'place_classtype_".$aSearch['sClass']."_".$aSearch['sType']."'";
1069 $bCacheTable = $oDB->getOne($sSQL);
1071 $sSQL = "select min(rank_search) from placex where place_id in ($sPlaceIDs)";
1073 if (CONST_Debug) var_dump($sSQL);
1074 $iMaxRank = ((int)$oDB->getOne($sSQL));
1076 // For state / country level searches the normal radius search doesn't work very well
1077 $sPlaceGeom = false;
1078 if ($iMaxRank < 9 && $bCacheTable)
1080 // Try and get a polygon to search in instead
1081 $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";
1082 if (CONST_Debug) var_dump($sSQL);
1083 $sPlaceGeom = $oDB->getOne($sSQL);
1093 $sSQL = "select place_id from placex where place_id in ($sPlaceIDs) and rank_search < $iMaxRank";
1094 if (CONST_Debug) var_dump($sSQL);
1095 $aPlaceIDs = $oDB->getCol($sSQL);
1096 $sPlaceIDs = join(',',$aPlaceIDs);
1099 if ($sPlaceIDs || $sPlaceGeom)
1105 // More efficient - can make the range bigger
1109 if ($sNearPointSQL) $sOrderBySQL = "ST_Distance($sNearPointSQL, l.centroid)";
1110 else if ($sPlaceIDs) $sOrderBySQL = "ST_Distance(l.centroid, f.geometry)";
1111 else if ($sPlaceGeom) $sOrderBysSQL = "ST_Distance(st_centroid('".$sPlaceGeom."'), l.centroid)";
1113 $sSQL = "select distinct l.place_id".($sOrderBySQL?','.$sOrderBySQL:'')." from place_classtype_".$aSearch['sClass']."_".$aSearch['sType']." as l";
1114 if ($sCountryCodesSQL) $sSQL .= " join placex as lp using (place_id)";
1117 $sSQL .= ",placex as f where ";
1118 $sSQL .= "f.place_id in ($sPlaceIDs) and ST_DWithin(l.centroid, f.centroid, $fRange) ";
1123 $sSQL .= "ST_Contains('".$sPlaceGeom."', l.centroid) ";
1125 if (sizeof($aExcludePlaceIDs))
1127 $sSQL .= " and l.place_id not in (".join(',',$aExcludePlaceIDs).")";
1129 if ($sCountryCodesSQL) $sSQL .= " and lp.calculated_country_code in ($sCountryCodesSQL)";
1130 if ($sOrderBySQL) $sSQL .= "order by ".$sOrderBySQL." asc";
1131 if ($iOffset) $sSQL .= " offset $iOffset";
1132 $sSQL .= " limit $iLimit";
1133 if (CONST_Debug) var_dump($sSQL);
1134 $aClassPlaceIDs = array_merge($aClassPlaceIDs, $oDB->getCol($sSQL));
1138 if (isset($aSearch['fRadius']) && $aSearch['fRadius']) $fRange = $aSearch['fRadius'];
1141 if ($sNearPointSQL) $sOrderBySQL = "ST_Distance($sNearPointSQL, l.geometry)";
1142 else $sOrderBySQL = "ST_Distance(l.geometry, f.geometry)";
1144 $sSQL = "select distinct l.place_id".($sOrderBysSQL?','.$sOrderBysSQL:'')." from placex as l,placex as f where ";
1145 $sSQL .= "f.place_id in ( $sPlaceIDs) and ST_DWithin(l.geometry, f.centroid, $fRange) ";
1146 $sSQL .= "and l.class='".$aSearch['sClass']."' and l.type='".$aSearch['sType']."' ";
1147 if (sizeof($aExcludePlaceIDs))
1149 $sSQL .= " and l.place_id not in (".join(',',$aExcludePlaceIDs).")";
1151 if ($sCountryCodesSQL) $sSQL .= " and l.calculated_country_code in ($sCountryCodesSQL)";
1152 if ($sOrderBy) $sSQL .= "order by ".$OrderBysSQL." asc";
1153 if ($iOffset) $sSQL .= " offset $iOffset";
1154 $sSQL .= " limit $iLimit";
1155 if (CONST_Debug) var_dump($sSQL);
1156 $aClassPlaceIDs = array_merge($aClassPlaceIDs, $oDB->getCol($sSQL));
1161 $aPlaceIDs = $aClassPlaceIDs;
1167 if (PEAR::IsError($aPlaceIDs))
1169 failInternalError("Could not get place IDs from tokens." ,$sSQL, $aPlaceIDs);
1172 if (CONST_Debug) { echo "<br><b>Place IDs:</b> "; var_Dump($aPlaceIDs); }
1174 foreach($aPlaceIDs as $iPlaceID)
1176 $aResultPlaceIDs[$iPlaceID] = $iPlaceID;
1178 if ($iQueryLoop > 20) break;
1181 if (isset($aResultPlaceIDs) && sizeof($aResultPlaceIDs) && ($iMinAddressRank != 0 || $iMaxAddressRank != 30))
1183 // Need to verify passes rank limits before dropping out of the loop (yuk!)
1184 $sSQL = "select place_id from placex where place_id in (".join(',',$aResultPlaceIDs).") ";
1185 $sSQL .= "and (placex.rank_address between $iMinAddressRank and $iMaxAddressRank ";
1186 if (14 >= $iMinAddressRank && 14 <= $iMaxAddressRank) $sSQL .= " OR (extratags->'place') = 'city'";
1187 if ($aAddressRankList) $sSQL .= " OR placex.rank_address in (".join(',',$aAddressRankList).")";
1188 $sSQL .= ") UNION select place_id from location_property_tiger where place_id in (".join(',',$aResultPlaceIDs).") ";
1189 $sSQL .= "and (30 between $iMinAddressRank and $iMaxAddressRank ";
1190 if ($aAddressRankList) $sSQL .= " OR 30 in (".join(',',$aAddressRankList).")";
1192 if (CONST_Debug) var_dump($sSQL);
1193 $aResultPlaceIDs = $oDB->getCol($sSQL);
1198 if (isset($aResultPlaceIDs) && sizeof($aResultPlaceIDs)) break;
1199 if ($iGroupLoop > 4) break;
1200 if ($iQueryLoop > 30) break;
1203 // Did we find anything?
1204 if (isset($aResultPlaceIDs) && sizeof($aResultPlaceIDs))
1206 //var_Dump($aResultPlaceIDs);exit;
1207 // Get the details for display (is this a redundant extra step?)
1208 $sPlaceIDs = join(',',$aResultPlaceIDs);
1209 $sImportanceSQL = '';
1210 if ($sViewboxSmallSQL) $sImportanceSQL .= " case when ST_Contains($sViewboxSmallSQL, ST_Collect(centroid)) THEN 1 ELSE 0.75 END * ";
1211 if ($sViewboxLargeSQL) $sImportanceSQL .= " case when ST_Contains($sViewboxLargeSQL, ST_Collect(centroid)) THEN 1 ELSE 0.75 END * ";
1213 $sOrderSQL = 'CASE ';
1214 foreach(array_keys($aResultPlaceIDs) as $iOrder => $iPlaceID)
1216 $sOrderSQL .= 'when min(place_id) = '.$iPlaceID.' then '.$iOrder.' ';
1218 $sOrderSQL .= ' ELSE 10000000 END';
1219 $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,";
1220 $sSQL .= "get_address_by_language(place_id, $sLanguagePrefArraySQL) as langaddress,";
1221 $sSQL .= "get_name_by_language(name, $sLanguagePrefArraySQL) as placename,";
1222 $sSQL .= "get_name_by_language(name, ARRAY['ref']) as ref,";
1223 $sSQL .= "avg(ST_X(centroid)) as lon,avg(ST_Y(centroid)) as lat, ";
1224 //$sSQL .= $sOrderSQL." as porder, ";
1225 $sSQL .= $sImportanceSQL."coalesce(importance,0.75-(rank_search::float/40)) as importance, ";
1226 $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, ";
1227 $sSQL .= "(extratags->'place') as extra_place ";
1228 $sSQL .= "from placex where place_id in ($sPlaceIDs) ";
1229 $sSQL .= "and (placex.rank_address between $iMinAddressRank and $iMaxAddressRank ";
1230 if (14 >= $iMinAddressRank && 14 <= $iMaxAddressRank) $sSQL .= " OR (extratags->'place') = 'city'";
1231 if ($aAddressRankList) $sSQL .= " OR placex.rank_address in (".join(',',$aAddressRankList).")";
1233 if ($sAllowedTypesSQLList) $sSQL .= "and placex.class in $sAllowedTypesSQLList ";
1234 $sSQL .= "and linked_place_id is null ";
1235 $sSQL .= "group by osm_type,osm_id,class,type,admin_level,rank_search,rank_address,calculated_country_code,importance";
1236 if (!$bDeDupe) $sSQL .= ",place_id";
1237 $sSQL .= ",langaddress ";
1238 $sSQL .= ",placename ";
1240 $sSQL .= ",extratags->'place' ";
1242 $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,";
1243 $sSQL .= "get_address_by_language(place_id, $sLanguagePrefArraySQL) as langaddress,";
1244 $sSQL .= "null as placename,";
1245 $sSQL .= "null as ref,";
1246 $sSQL .= "avg(ST_X(centroid)) as lon,avg(ST_Y(centroid)) as lat, ";
1247 //$sSQL .= $sOrderSQL." as porder, ";
1248 $sSQL .= $sImportanceSQL."0.015 as importance, ";
1249 $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, ";
1250 $sSQL .= "null as extra_place ";
1251 $sSQL .= "from location_property_tiger where place_id in ($sPlaceIDs) ";
1252 $sSQL .= "and 30 between $iMinAddressRank and $iMaxAddressRank ";
1253 $sSQL .= "group by place_id";
1254 if (!$bDeDupe) $sSQL .= ",place_id";
1257 $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,";
1258 $sSQL .= "get_address_by_language(place_id, $sLanguagePrefArraySQL) as langaddress,";
1259 $sSQL .= "null as placename,";
1260 $sSQL .= "null as ref,";
1261 $sSQL .= "avg(ST_X(centroid)) as lon,avg(ST_Y(centroid)) as lat, ";
1262 //$sSQL .= $sOrderSQL." as porder, ";
1263 $sSQL .= $sImportanceSQL."0.01 as importance, ";
1264 $sSQL .= "(select max(p.importance*(p.rank_address+2)) from place_addressline s, placex p where s.place_id = min(location_property_aux.place_id) and p.place_id = s.address_place_id and s.isaddress and p.importance is not null) as addressimportance, ";
1265 $sSQL .= "null as extra_place ";
1266 $sSQL .= "from location_property_aux where place_id in ($sPlaceIDs) ";
1267 $sSQL .= "and 30 between $iMinAddressRank and $iMaxAddressRank ";
1268 $sSQL .= "group by place_id";
1269 if (!$bDeDupe) $sSQL .= ",place_id";
1270 $sSQL .= ",get_address_by_language(place_id, $sLanguagePrefArraySQL) ";
1271 $sSQL .= "order by importance desc";
1272 //$sSQL .= "order by rank_search,rank_address,porder asc";
1274 if (CONST_Debug) { echo "<hr>"; var_dump($sSQL); }
1275 $aSearchResults = $oDB->getAll($sSQL);
1276 //var_dump($sSQL,$aSearchResults);exit;
1278 if (PEAR::IsError($aSearchResults))
1280 failInternalError("Could not get details for place.", $sSQL, $aSearchResults);
1283 } // end if ($sQuery)
1286 if (isset($_GET['nearlat']) && trim($_GET['nearlat'])!=='' && isset($_GET['nearlon']) && trim($_GET['nearlon']) !== '')
1288 $iPlaceID = geocodeReverse((float)$_GET['nearlat'], (float)$_GET['nearlon']);
1292 $aResultPlaceIDs = array($iPlaceID);
1293 // TODO: this needs refactoring!
1295 // Get the details for display (is this a redundant extra step?)
1296 $sPlaceIDs = join(',',$aResultPlaceIDs);
1297 $sOrderSQL = 'CASE ';
1298 foreach(array_keys($aResultPlaceIDs) as $iOrder => $iPlaceID)
1300 $sOrderSQL .= 'when min(place_id) = '.$iPlaceID.' then '.$iOrder.' ';
1302 $sOrderSQL .= ' ELSE 10000000 END';
1303 $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,";
1304 $sSQL .= "get_address_by_language(place_id, $sLanguagePrefArraySQL) as langaddress,";
1305 $sSQL .= "get_name_by_language(name, $sLanguagePrefArraySQL) as placename,";
1306 $sSQL .= "get_name_by_language(name, ARRAY['ref']) as ref,";
1307 $sSQL .= "avg(ST_X(centroid)) as lon,avg(ST_Y(centroid)) as lat, ";
1308 //$sSQL .= $sOrderSQL." as porder, ";
1309 $sSQL .= "coalesce(importance,0.75-(rank_search::float/40)) as importance, ";
1310 $sSQL .= "(extratags->'place') as extra_place ";
1311 $sSQL .= "from placex where place_id in ($sPlaceIDs) ";
1312 $sSQL .= "and (placex.rank_address between $iMinAddressRank and $iMaxAddressRank ";
1313 if (14 >= $iMinAddressRank && 14 <= $iMaxAddressRank) $sSQL .= " OR (extratags->'place') = 'city'";
1314 if ($aAddressRankList) $sSQL .= " OR placex.rank_address in (".join(',',$aAddressRankList).")";
1316 $sSQL .= "group by osm_type,osm_id,class,type,admin_level,rank_search,rank_address,calculated_country_code,importance";
1317 if (!$bDeDupe) $sSQL .= ",place_id";
1318 $sSQL .= ",get_address_by_language(place_id, $sLanguagePrefArraySQL) ";
1319 $sSQL .= ",get_name_by_language(name, $sLanguagePrefArraySQL) ";
1320 $sSQL .= ",get_name_by_language(name, ARRAY['ref']) ";
1321 $sSQL .= ",extratags->'place' ";
1323 $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,";
1324 $sSQL .= "get_address_by_language(place_id, $sLanguagePrefArraySQL) as langaddress,";
1325 $sSQL .= "null as placename,";
1326 $sSQL .= "null as ref,";
1327 $sSQL .= "avg(ST_X(centroid)) as lon,avg(ST_Y(centroid)) as lat, ";
1328 //$sSQL .= $sOrderSQL." as porder, ";
1329 $sSQL .= "-0.15 as importance, ";
1330 $sSQL .= "null as extra_place ";
1331 $sSQL .= "from location_property_tiger where place_id in ($sPlaceIDs) ";
1332 $sSQL .= "and 30 between $iMinAddressRank and $iMaxAddressRank ";
1333 $sSQL .= "group by place_id";
1334 if (!$bDeDupe) $sSQL .= ",place_id";
1337 $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,";
1338 $sSQL .= "get_address_by_language(place_id, $sLanguagePrefArraySQL) as langaddress,";
1339 $sSQL .= "null as placename,";
1340 $sSQL .= "null as ref,";
1341 $sSQL .= "avg(ST_X(centroid)) as lon,avg(ST_Y(centroid)) as lat, ";
1342 //$sSQL .= $sOrderSQL." as porder, ";
1343 $sSQL .= "-0.10 as importance, ";
1344 $sSQL .= "null as extra_place ";
1345 $sSQL .= "from location_property_aux where place_id in ($sPlaceIDs) ";
1346 $sSQL .= "and 30 between $iMinAddressRank and $iMaxAddressRank ";
1347 $sSQL .= "group by place_id";
1349 if (!$bDeDupe) $sSQL .= ",place_id";
1350 $sSQL .= ",get_address_by_language(place_id, $sLanguagePrefArraySQL) ";
1351 $sSQL .= "order by importance desc";
1352 //$sSQL .= "order by rank_search,rank_address,porder asc";
1353 if (CONST_Debug) { echo "<hr>", var_dump($sSQL); }
1354 $aSearchResults = $oDB->getAll($sSQL);
1355 //var_dump($sSQL,$aSearchResults);exit;
1357 if (PEAR::IsError($aSearchResults))
1359 failInternalError("Could not get details for place (near).", $sSQL, $aSearchResults);
1364 $aSearchResults = array();
1370 $sSearchResult = '';
1371 if (!sizeof($aSearchResults) && isset($_GET['q']) && $_GET['q'])
1373 $sSearchResult = 'No Results Found';
1375 //var_Dump($aSearchResults);
1377 $aClassType = getClassTypesWithImportance();
1378 $aRecheckWords = preg_split('/\b/u',$sQuery);
1379 foreach($aRecheckWords as $i => $sWord)
1381 if (!$sWord) unset($aRecheckWords[$i]);
1383 foreach($aSearchResults as $iResNum => $aResult)
1385 if (CONST_Search_AreaPolygons)
1387 // Get the bounding box and outline polygon
1388 $sSQL = "select place_id,numfeatures,area,outline,";
1389 $sSQL .= "ST_Y(ST_PointN(ST_ExteriorRing(Box2D(outline)),4)) as minlat,ST_Y(ST_PointN(ST_ExteriorRing(Box2D(outline)),2)) as maxlat,";
1390 $sSQL .= "ST_X(ST_PointN(ST_ExteriorRing(Box2D(outline)),1)) as minlon,ST_X(ST_PointN(ST_ExteriorRing(Box2D(outline)),3)) as maxlon,";
1391 $sSQL .= "ST_AsText(outline) as outlinestring from get_place_boundingbox_quick(".$aResult['place_id'].")";
1393 $sSQL = "select place_id,0 as numfeatures,st_area(geometry) as area,";
1394 $sSQL .= "ST_Y(centroid) as centrelat,ST_X(centroid) as centrelon,";
1395 $sSQL .= "ST_Y(ST_PointN(ST_ExteriorRing(Box2D(geometry)),4)) as minlat,ST_Y(ST_PointN(ST_ExteriorRing(Box2D(geometry)),2)) as maxlat,";
1396 $sSQL .= "ST_X(ST_PointN(ST_ExteriorRing(Box2D(geometry)),1)) as minlon,ST_X(ST_PointN(ST_ExteriorRing(Box2D(geometry)),3)) as maxlon";
1397 if ($bAsGeoJSON) $sSQL .= ",ST_AsGeoJSON(geometry) as asgeojson";
1398 if ($bAsKML) $sSQL .= ",ST_AsKML(geometry) as askml";
1399 if ($bAsSVG) $sSQL .= ",ST_AsSVG(geometry) as assvg";
1400 if ($bAsText || $bShowPolygons) $sSQL .= ",ST_AsText(geometry) as astext";
1401 $sSQL .= " from placex where place_id = ".$aResult['place_id'].' and st_geometrytype(Box2D(geometry)) = \'ST_Polygon\'';
1402 $aPointPolygon = $oDB->getRow($sSQL);
1403 if (PEAR::IsError($aPointPolygon))
1405 failInternalError("Could not get outline.", $sSQL, $aPointPolygon);
1407 if ($aPointPolygon['place_id'])
1409 if ($bAsGeoJSON) $aResult['asgeojson'] = $aPointPolygon['asgeojson'];
1410 if ($bAsKML) $aResult['askml'] = $aPointPolygon['askml'];
1411 if ($bAsSVG) $aResult['assvg'] = $aPointPolygon['assvg'];
1412 if ($bAsText) $aResult['astext'] = $aPointPolygon['astext'];
1414 if ($aPointPolygon['centrelon'] !== null && $aPointPolygon['centrelat'] !== null )
1416 $aResult['lat'] = $aPointPolygon['centrelat'];
1417 $aResult['lon'] = $aPointPolygon['centrelon'];
1421 // Translate geometary string to point array
1422 if (preg_match('#POLYGON\\(\\(([- 0-9.,]+)#',$aPointPolygon['astext'],$aMatch))
1424 preg_match_all('/(-?[0-9.]+) (-?[0-9.]+)/',$aMatch[1],$aPolyPoints,PREG_SET_ORDER);
1426 /*elseif (preg_match('#MULTIPOLYGON\\(\\(\\(([- 0-9.,]+)#',$aPointPolygon['astext'],$aMatch))
1428 preg_match_all('/(-?[0-9.]+) (-?[0-9.]+)/',$aMatch[1],$aPolyPoints,PREG_SET_ORDER);
1430 elseif (preg_match('#POINT\\((-?[0-9.]+) (-?[0-9.]+)\\)#',$aPointPolygon['astext'],$aMatch))
1433 $iSteps = ($fRadius * 40000)^2;
1434 $fStepSize = (2*pi())/$iSteps;
1435 $aPolyPoints = array();
1436 for($f = 0; $f < 2*pi(); $f += $fStepSize)
1438 $aPolyPoints[] = array('',$aMatch[1]+($fRadius*sin($f)),$aMatch[2]+($fRadius*cos($f)));
1440 $aPointPolygon['minlat'] = $aPointPolygon['minlat'] - $fRadius;
1441 $aPointPolygon['maxlat'] = $aPointPolygon['maxlat'] + $fRadius;
1442 $aPointPolygon['minlon'] = $aPointPolygon['minlon'] - $fRadius;
1443 $aPointPolygon['maxlon'] = $aPointPolygon['maxlon'] + $fRadius;
1447 // Output data suitable for display (points and a bounding box)
1448 if ($bShowPolygons && isset($aPolyPoints))
1450 $aResult['aPolyPoints'] = array();
1451 foreach($aPolyPoints as $aPoint)
1453 $aResult['aPolyPoints'][] = array($aPoint[1], $aPoint[2]);
1456 $aResult['aBoundingBox'] = array($aPointPolygon['minlat'],$aPointPolygon['maxlat'],$aPointPolygon['minlon'],$aPointPolygon['maxlon']);
1460 if ($aResult['extra_place'] == 'city')
1462 $aResult['class'] = 'place';
1463 $aResult['type'] = 'city';
1464 $aResult['rank_search'] = 16;
1467 if (!isset($aResult['aBoundingBox']))
1470 $fDiameter = 0.0001;
1472 if (isset($aClassType[$aResult['class'].':'.$aResult['type'].':'.$aResult['admin_level']]['defdiameter'])
1473 && $aClassType[$aResult['class'].':'.$aResult['type'].':'.$aResult['admin_level']]['defdiameter'])
1475 $fDiameter = $aClassType[$aResult['class'].':'.$aResult['type'].':'.$aResult['admin_level']]['defzoom'];
1477 elseif (isset($aClassType[$aResult['class'].':'.$aResult['type']]['defdiameter'])
1478 && $aClassType[$aResult['class'].':'.$aResult['type']]['defdiameter'])
1480 $fDiameter = $aClassType[$aResult['class'].':'.$aResult['type']]['defdiameter'];
1482 $fRadius = $fDiameter / 2;
1484 $iSteps = max(8,min(100,$fRadius * 3.14 * 100000));
1485 $fStepSize = (2*pi())/$iSteps;
1486 $aPolyPoints = array();
1487 for($f = 0; $f < 2*pi(); $f += $fStepSize)
1489 $aPolyPoints[] = array('',$aResult['lon']+($fRadius*sin($f)),$aResult['lat']+($fRadius*cos($f)));
1491 $aPointPolygon['minlat'] = $aResult['lat'] - $fRadius;
1492 $aPointPolygon['maxlat'] = $aResult['lat'] + $fRadius;
1493 $aPointPolygon['minlon'] = $aResult['lon'] - $fRadius;
1494 $aPointPolygon['maxlon'] = $aResult['lon'] + $fRadius;
1496 // Output data suitable for display (points and a bounding box)
1499 $aResult['aPolyPoints'] = array();
1500 foreach($aPolyPoints as $aPoint)
1502 $aResult['aPolyPoints'][] = array($aPoint[1], $aPoint[2]);
1505 $aResult['aBoundingBox'] = array($aPointPolygon['minlat'],$aPointPolygon['maxlat'],$aPointPolygon['minlon'],$aPointPolygon['maxlon']);
1508 // Is there an icon set for this type of result?
1509 if (isset($aClassType[$aResult['class'].':'.$aResult['type']]['icon'])
1510 && $aClassType[$aResult['class'].':'.$aResult['type']]['icon'])
1512 $aResult['icon'] = CONST_Website_BaseURL.'images/mapicons/'.$aClassType[$aResult['class'].':'.$aResult['type']]['icon'].'.p.20.png';
1515 if (isset($aClassType[$aResult['class'].':'.$aResult['type']]['label'])
1516 && $aClassType[$aResult['class'].':'.$aResult['type']]['label'])
1518 $aResult['label'] = $aClassType[$aResult['class'].':'.$aResult['type']]['label'];
1521 if ($bShowAddressDetails)
1523 $aResult['address'] = getAddressDetails($oDB, $sLanguagePrefArraySQL, $aResult['place_id'], $aResult['country_code']);
1524 if ($aResult['extra_place'] == 'city' && !isset($aResult['address']['city']))
1526 $aResult['address'] = array_merge(array('city' => array_shift(array_values($aResult['address']))), $aResult['address']);
1529 //var_dump($aResult['address']);
1533 // Adjust importance for the number of exact string matches in the result
1534 $aResult['importance'] = max(0.001,$aResult['importance']);
1536 $sAddress = $aResult['langaddress'];
1537 foreach($aRecheckWords as $i => $sWord)
1539 if (stripos($sAddress, $sWord)!==false) $iCountWords++;
1542 $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
1544 //if (CONST_Debug) var_dump($aResult['class'].':'.$aResult['type'].':'.$aResult['admin_level']);
1546 if (isset($aClassType[$aResult['class'].':'.$aResult['type'].':'.$aResult['admin_level']]['importance'])
1547 && $aClassType[$aResult['class'].':'.$aResult['type'].':'.$aResult['admin_level']]['importance'])
1549 $aResult['importance'] = $aClassType[$aResult['class'].':'.$aResult['type'].':'.$aResult['admin_level']]['importance'];
1551 elseif (isset($aClassType[$aResult['class'].':'.$aResult['type']]['importance'])
1552 && $aClassType[$aResult['class'].':'.$aResult['type']]['importance'])
1554 $aResult['importance'] = $aClassType[$aResult['class'].':'.$aResult['type']]['importance'];
1558 $aResult['importance'] = 1000000000000000;
1561 $aResult['name'] = $aResult['langaddress'];
1562 $aResult['foundorder'] = -$aResult['addressimportance'];
1563 $aSearchResults[$iResNum] = $aResult;
1565 uasort($aSearchResults, 'byImportance');
1567 $aOSMIDDone = array();
1568 $aClassTypeNameDone = array();
1569 $aToFilter = $aSearchResults;
1570 $aSearchResults = array();
1573 foreach($aToFilter as $iResNum => $aResult)
1575 if ($aResult['type'] == 'adminitrative') $aResult['type'] = 'administrative';
1576 $aExcludePlaceIDs[$aResult['place_id']] = $aResult['place_id'];
1579 $fLat = $aResult['lat'];
1580 $fLon = $aResult['lon'];
1581 if (isset($aResult['zoom'])) $iZoom = $aResult['zoom'];
1584 if (!$bDeDupe || (!isset($aOSMIDDone[$aResult['osm_type'].$aResult['osm_id']])
1585 && !isset($aClassTypeNameDone[$aResult['osm_type'].$aResult['class'].$aResult['type'].$aResult['name'].$aResult['admin_level']])))
1587 $aOSMIDDone[$aResult['osm_type'].$aResult['osm_id']] = true;
1588 $aClassTypeNameDone[$aResult['osm_type'].$aResult['class'].$aResult['type'].$aResult['name'].$aResult['admin_level']] = true;
1589 $aSearchResults[] = $aResult;
1592 // Absolute limit on number of results
1593 if (sizeof($aSearchResults) >= $iFinalLimit) break;
1596 $sDataDate = $oDB->getOne("select TO_CHAR(lastimportdate - '2 minutes'::interval,'YYYY/MM/DD HH24:MI')||' GMT' from import_status limit 1");
1598 if (isset($_GET['nearlat']) && isset($_GET['nearlon']))
1600 $sQuery .= ' ['.$_GET['nearlat'].','.$_GET['nearlon'].']';
1605 logEnd($oDB, $hLog, sizeof($aToFilter));
1607 $sMoreURL = CONST_Website_BaseURL.'search?format='.urlencode($sOutputFormat).'&exclude_place_ids='.join(',',$aExcludePlaceIDs);
1608 if (isset($_SERVER["HTTP_ACCEPT_LANGUAGE"])) $sMoreURL .= '&accept-language='.$_SERVER["HTTP_ACCEPT_LANGUAGE"];
1609 if ($bShowPolygons) $sMoreURL .= '&polygon=1';
1610 if ($bShowAddressDetails) $sMoreURL .= '&addressdetails=1';
1611 if (isset($_GET['viewbox']) && $_GET['viewbox']) $sMoreURL .= '&viewbox='.urlencode($_GET['viewbox']);
1612 if (isset($_GET['nearlat']) && isset($_GET['nearlon'])) $sMoreURL .= '&nearlat='.(float)$_GET['nearlat'].'&nearlon='.(float)$_GET['nearlon'];
1613 $sMoreURL .= '&q='.urlencode($sQuery);
1615 if (CONST_Debug) exit;
1617 include(CONST_BasePath.'/lib/template/search-'.$sOutputFormat.'.php');