X-Git-Url: https://git.openstreetmap.org./nominatim.git/blobdiff_plain/c8c8471e737ff9344d98296cf6c238f9f659b3d4..c0d9ba7e0cca0e1ec0ebd6764c5d5c871e5e443b:/website/search.php diff --git a/website/search.php b/website/search.php index c00c4cfd..1d544df4 100755 --- a/website/search.php +++ b/website/search.php @@ -36,8 +36,8 @@ // Show address breakdown $bShowAddressDetails = isset($_GET['addressdetails']) && $_GET['addressdetails']; - // Prefered language - $aLangPrefOrder = getPrefferedLangauges(); + // Preferred language + $aLangPrefOrder = getPreferredLanguages(); if (isset($aLangPrefOrder['name:de'])) $bReverseInPlan = true; if (isset($aLangPrefOrder['name:ru'])) $bReverseInPlan = true; if (isset($aLangPrefOrder['name:ja'])) $bReverseInPlan = true; @@ -53,7 +53,7 @@ } } - // Only certain ranks of feature + // Only certain ranks of feature if (isset($_GET['featureType']) && !isset($_GET['featuretype'])) $_GET['featuretype'] = $_GET['featureType']; if (isset($_GET['featuretype'])) @@ -92,15 +92,48 @@ // Search query $sQuery = (isset($_GET['q'])?trim($_GET['q']):''); - if (!$sQuery && $_SERVER['PATH_INFO'] && $_SERVER['PATH_INFO'][0] == '/') + if (!$sQuery && isset($_SERVER['PATH_INFO']) && $_SERVER['PATH_INFO'][0] == '/') { $sQuery = substr($_SERVER['PATH_INFO'], 1); - // reverse order of '/' seperated string + // reverse order of '/' separated string $aPhrases = explode('/', $sQuery); $aPhrases = array_reverse($aPhrases); $sQuery = join(', ',$aPhrases); } + + function structuredAddressElement(&$aStructuredQuery, &$iMinAddressRank, &$iMaxAddressRank, $aParams, $sKey, $iNewMinAddressRank, $iNewMaxAddressRank) + { + if (!isset($_GET[$sKey])) return false; + $sValue = trim($_GET[$sKey]); + if (!$sValue) return false; + $aStructuredQuery[$sKey] = $sValue; + if ($iMinAddressRank == 0 && $iMaxAddressRank == 30) { + $iMinAddressRank = $iNewMinAddressRank; + $iMaxAddressRank = $iNewMaxAddressRank; + } + return true; + } + + // Structured query? + $aStructuredOptions = array( + array('amenity', 26, 30), + array('street', 26, 30), + array('city', 14, 24), + array('county', 9, 13), + array('state', 8, 8), + array('country', 4, 4), + array('postalcode', 16, 25), + ); + $aStructuredQuery = array(); + foreach($aStructuredOptions as $aStructuredOption) + { + loadStructuredAddressElement($aStructuredQuery, $iMinAddressRank, $iMaxAddressRank, $_GET, $aStructuredOption[0], $aStructuredOption[1], $aStructuredOption[2]); + } + if (sizeof($aStructuredQuery) > 0) { + $sQuery = join(', ', $aStructuredQuery); + } + if ($sQuery) { $hLog = logStart($oDB, 'search', $sQuery, $aLangPrefOrder); @@ -109,10 +142,9 @@ $sQuery = str_ireplace(array('New York, ny','new york, new york', 'New York ny','new york new york'), 'new york city, ny', $sQuery); if (isset($aLangPrefOrder['name:en'])) { - $sQuery = preg_replace('/\\bil\\b/','illinois', $sQuery); - $sQuery = preg_replace('/\\bal\\b/','alabama', $sQuery); - $sQuery = preg_replace('/\\bla\\b/','louisiana', $sQuery); - $sQuery = preg_replace('/\\bde louisiana\\b/','de la', $sQuery); + $sQuery = preg_replace('/,\s*il\s*(,|$)/',', illinois\1', $sQuery); + $sQuery = preg_replace('/,\s*al\s*(,|$)/',', alabama\1', $sQuery); + $sQuery = preg_replace('/,\s*la\s*(,|$)/',', louisiana\1', $sQuery); } // If we have a view box create the SQL @@ -134,6 +166,8 @@ $aCoOrdinates[1] += $fWidth; $aCoOrdinates[3] -= $fWidth; $sViewboxLargeSQL = "ST_SetSRID(ST_MakeBox2D(ST_Point(".(float)$aCoOrdinates[0].",".(float)$aCoOrdinates[1]."),ST_Point(".(float)$aCoOrdinates[2].",".(float)$aCoOrdinates[3].")),4326)"; + } else { + $bBoundingBoxSearch = false; } if (isset($_GET['route']) && $_GET['route'] && isset($_GET['routewidth']) && $_GET['routewidth']) { @@ -211,7 +245,7 @@ } } - if ($sQuery) + if ($sQuery || $aStructuredQuery) { // Start with a blank search $aSearches = array( @@ -239,12 +273,17 @@ preg_match_all('/\\[([a-zA-Z]*)\\]/', $sQuery, $aSpecialTermsRaw, PREG_SET_ORDER); $aSpecialTerms = array(); + if (isset($aStructuredQuery['amenity']) && $aStructuredQuery['amenity']) + { + $aSpecialTermsRaw[] = array('['.$aStructuredQuery['amenity'].']', $aStructuredQuery['amenity']); + unset($aStructuredQuery['amenity']); + } foreach($aSpecialTermsRaw as $aSpecialTerm) { $sQuery = str_replace($aSpecialTerm[0], ' ', $sQuery); $sToken = $oDB->getOne("select make_standard_name('".$aSpecialTerm[1]."') as string"); $sSQL = 'select * from (select word_id,word_token, word, class, type, location, country_code, operator'; - $sSQL .= ' from word where word_token in (\' '.$sToken.'\')) as x where (class is not null and class not in (\'place\',\'highway\')) or country_code is not null'; + $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'; if (CONST_Debug) var_Dump($sSQL); $aSearchWords = $oDB->getAll($sSQL); $aNewSearches = array(); @@ -273,7 +312,17 @@ // Split query into phrases // Commas are used to reduce the search space by indicating where phrases split - $aPhrases = explode(',',$sQuery); + if (sizeof($aStructuredQuery) > 0) + { + $aPhrases = $aStructuredQuery; + $bStructuredPhrases = true; + } + else + { + $aPhrases = explode(',',$sQuery); + $bStructuredPhrases = false; + } + // Convert each phrase to standard form // Create a list of standard words @@ -303,6 +352,7 @@ } // reindex phrases - we make assumptions later on + $aPhraseTypes = array_keys($aPhrases); $aPhrases = array_values($aPhrases); if (sizeof($aTokens)) @@ -311,7 +361,7 @@ // Check which tokens we have, get the ID numbers $sSQL = 'select word_id,word_token, word, class, type, location, country_code, operator'; $sSQL .= ' from word where word_token in ('.join(',',array_map("getDBQuoted",$aTokens)).')'; - $sSQL .= ' and (class is null or class not in (\'highway\'))'; + $sSQL .= ' and search_name_count < '.CONST_Max_Word_Frequency; // $sSQL .= ' group by word_token, word, class, type, location, country_code'; if (CONST_Debug) var_Dump($sSQL); @@ -407,28 +457,30 @@ Calculate all searches using aValidTokens i.e. 'Wodsworth Road, Sheffield' => - + Phrase Wordset 0 0 (wodsworth road) 0 1 (wodsworth)(road) 1 0 (sheffield) - + Score how good the search is so they can be ordered */ foreach($aPhrases as $iPhrase => $sPhrase) { $aNewPhraseSearches = array(); + if ($bStructuredPhrases) $sPhraseType = $aPhraseTypes[$iPhrase]; + else $sPhraseType = ''; - foreach($aPhrases[$iPhrase]['wordsets'] as $iWordset => $aWordset) + foreach($aPhrases[$iPhrase]['wordsets'] as $aWordset) { $aWordsetSearches = $aSearches; // Add all words from this wordset - foreach($aWordset as $sToken) + foreach($aWordset as $iToken => $sToken) { //echo "
$sToken"; $aNewWordsetSearches = array(); - + foreach($aWordsetSearches as $aCurrentSearch) { //echo ""; @@ -442,17 +494,17 @@ { $aSearch = $aCurrentSearch; $aSearch['iSearchRank']++; - if ($aSearchTerm['country_code'] !== null && $aSearchTerm['country_code'] != '0') + if (($sPhraseType == '' || $sPhraseType == 'country') && $aSearchTerm['country_code'] !== null && $aSearchTerm['country_code'] != '0') { if ($aSearch['sCountryCode'] === false) { $aSearch['sCountryCode'] = strtolower($aSearchTerm['country_code']); - // Country is almost always at the end of the string - increase score for finding it anywhere else (opimisation) - if ($iWordset+1 != sizeof($aPhrases[$iPhrase]['wordsets']) || $iPhrase+1 != sizeof($aPhrases)) $aSearch['iSearchRank'] += 5; + // Country is almost always at the end of the string - increase score for finding it anywhere else (optimisation) + if ($iToken+1 != sizeof($aWordset) || $iPhrase+1 != sizeof($aPhrases)) $aSearch['iSearchRank'] += 5; if ($aSearch['iSearchRank'] < $iMaxRank) $aNewWordsetSearches[] = $aSearch; } } - elseif ($aSearchTerm['lat'] !== '' && $aSearchTerm['lat'] !== null) + elseif (isset($aSearchTerm['lat']) && $aSearchTerm['lat'] !== '' && $aSearchTerm['lat'] !== null) { if ($aSearch['fLat'] === '') { @@ -462,7 +514,7 @@ if ($aSearch['iSearchRank'] < $iMaxRank) $aNewWordsetSearches[] = $aSearch; } } - elseif ($aSearchTerm['class'] == 'place' && $aSearchTerm['type'] == 'house') + elseif (($sPhraseType == '' || $sPhraseType == 'street') && $aSearchTerm['class'] == 'place' && $aSearchTerm['type'] == 'house') { if ($aSearch['sHouseNumber'] === '') { @@ -476,7 +528,7 @@ */ } } - elseif ($aSearchTerm['class'] !== '' && $aSearchTerm['class'] !== null) + elseif ($sPhraseType == '' && $aSearchTerm['class'] !== '' && $aSearchTerm['class'] !== null) { if ($aSearch['sClass'] === '') { @@ -505,7 +557,7 @@ { if (sizeof($aSearch['aName'])) { - if (!isset($aValidTokens[$sToken]) || strlen($sToken) < 4 || strpos($sToken, ' ') !== false) + if (($sPhraseType != 'street' && $sPhraseType != 'country') && (!isset($aValidTokens[$sToken]) || strlen($sToken) < 4 || strpos($sToken, ' ') !== false)) { $aSearch['aAddress'][$aSearchTerm['word_id']] = $aSearchTerm['word_id']; } @@ -530,25 +582,23 @@ { if (isset($aSearchTerm['word_id']) && $aSearchTerm['word_id']) { -//var_Dump('
',$aSearch['aName']); - - if (sizeof($aCurrentSearch['aName']) && strlen($sToken) >= 4) - { - $aSearch = $aCurrentSearch; - $aSearch['iSearchRank'] += 1; - $aSearch['aAddress'][$aSearchTerm['word_id']] = $aSearchTerm['word_id']; - if ($aSearch['iSearchRank'] < $iMaxRank) $aNewWordsetSearches[] = $aSearch; - } + if (($sPhraseType != 'street') && sizeof($aCurrentSearch['aName']) && strlen($sToken) >= 4) + { + $aSearch = $aCurrentSearch; + $aSearch['iSearchRank'] += 1; + $aSearch['aAddress'][$aSearchTerm['word_id']] = $aSearchTerm['word_id']; + if ($aSearch['iSearchRank'] < $iMaxRank) $aNewWordsetSearches[] = $aSearch; + } - if (!sizeof($aCurrentSearch['aName']) || $aCurrentSearch['iNamePhrase'] == $iPhrase) - { - $aSearch = $aCurrentSearch; - $aSearch['iSearchRank'] += 2; - if (preg_match('#^[0-9]+$#', $sToken)) $aSearch['iSearchRank'] += 2; - $aSearch['aName'][$aSearchTerm['word_id']] = $aSearchTerm['word_id']; - $aSearch['iNamePhrase'] = $iPhrase; - if ($aSearch['iSearchRank'] < $iMaxRank) $aNewWordsetSearches[] = $aSearch; - } + if (!sizeof($aCurrentSearch['aName']) || $aCurrentSearch['iNamePhrase'] == $iPhrase) + { + $aSearch = $aCurrentSearch; + $aSearch['iSearchRank'] += 2; + if (preg_match('#^[0-9]+$#', $sToken)) $aSearch['iSearchRank'] += 2; + $aSearch['aName'][$aSearchTerm['word_id']] = $aSearchTerm['word_id']; + $aSearch['iNamePhrase'] = $iPhrase; + if ($aSearch['iSearchRank'] < $iMaxRank) $aNewWordsetSearches[] = $aSearch; + } } } } @@ -704,22 +754,22 @@ $sSQL = "select count(*) from pg_tables where tablename = 'place_classtype_".$aSearch['sClass']."_".$aSearch['sType']."'"; if ($oDB->getOne($sSQL)) { - $sSQL = "select place_id from place_classtype_".$aSearch['sClass']."_".$aSearch['sType']; + $sSQL = "select place_id from place_classtype_".$aSearch['sClass']."_".$aSearch['sType']." ct"; if ($sCountryCodesSQL) $sSQL .= " join placex using (place_id)"; - $sSQL .= " where st_contains($sViewboxSmallSQL, centroid)"; + $sSQL .= " where st_contains($sViewboxSmallSQL, ct.centroid) and linked_place_id is null"; if ($sCountryCodesSQL) $sSQL .= " and country_code in ($sCountryCodesSQL)"; - if ($sViewboxCentreSQL) $sSQL .= " order by st_distance($sViewboxCentreSQL, centroid) asc"; + if ($sViewboxCentreSQL) $sSQL .= " order by st_distance($sViewboxCentreSQL, ct.centroid) asc"; $sSQL .= " limit $iLimit"; if (CONST_Debug) var_dump($sSQL); $aPlaceIDs = $oDB->getCol($sSQL); if (!sizeof($aPlaceIDs)) { - $sSQL = "select place_id from place_classtype_".$aSearch['sClass']."_".$aSearch['sType']; + $sSQL = "select place_id from place_classtype_".$aSearch['sClass']."_".$aSearch['sType']." ct"; if ($sCountryCodesSQL) $sSQL .= " join placex using (place_id)"; - $sSQL .= " where st_contains($sViewboxLargeSQL, centroid)"; + $sSQL .= " where st_contains($sViewboxLargeSQL, ct.centroid) and linked_place_id is null"; if ($sCountryCodesSQL) $sSQL .= " and country_code in ($sCountryCodesSQL)"; - if ($sViewboxCentreSQL) $sSQL .= " order by st_distance($sViewboxCentreSQL, centroid) asc"; + if ($sViewboxCentreSQL) $sSQL .= " order by st_distance($sViewboxCentreSQL, ct.centroid) asc"; $sSQL .= " limit $iLimit"; if (CONST_Debug) var_dump($sSQL); $aPlaceIDs = $oDB->getCol($sSQL); @@ -728,7 +778,7 @@ else { $sSQL = "select place_id from placex where class='".$aSearch['sClass']."' and type='".$aSearch['sType']."'"; - $sSQL .= " and st_contains($sViewboxSmallSQL, centroid)"; + $sSQL .= " and st_contains($sViewboxSmallSQL, geometry) and linked_place_id is null"; if ($sCountryCodesSQL) $sSQL .= " and country_code in ($sCountryCodesSQL)"; if ($sViewboxCentreSQL) $sSQL .= " order by st_distance($sViewboxCentreSQL, centroid) asc"; $sSQL .= " limit $iLimit"; @@ -817,7 +867,7 @@ $sPlaceIDs = join(',',$aPlaceIDs); // Now they are indexed look for a house attached to a street we found - $sHouseNumberRegex = '\\\\m'.str_replace(' ','[-, ]',$aSearch['sHouseNumber']).'\\\\M'; + $sHouseNumberRegex = '\\\\m'.str_replace(' ','[-,/ ]',$aSearch['sHouseNumber']).'\\\\M'; $sSQL = "select place_id from placex where parent_place_id in (".$sPlaceIDs.") and housenumber ~* E'".$sHouseNumberRegex."'"; if (sizeof($aExcludePlaceIDs)) { @@ -868,6 +918,7 @@ { // If they were searching for a named class (i.e. 'Kings Head pub') then we might have an extra match $sSQL = "select place_id from placex where place_id in ($sPlaceIDs) and class='".$aSearch['sClass']."' and type='".$aSearch['sType']."'"; + $sSQL = " and linked_place_id is null"; if ($sCountryCodesSQL) $sSQL .= " and country_code in ($sCountryCodesSQL)"; $sSQL .= " order by rank_search asc limit $iLimit"; if (CONST_Debug) var_dump($sSQL); @@ -1012,6 +1063,7 @@ $sSQL .= "coalesce(importance,0.9-(rank_search::float/30)) as importance "; $sSQL .= "from placex where place_id in ($sPlaceIDs) "; $sSQL .= "and placex.rank_address between $iMinAddressRank and $iMaxAddressRank "; + $sSQL .= "and linked_place_id is null "; $sSQL .= "group by osm_type,osm_id,class,type,admin_level,rank_search,rank_address,country_code,importance"; if (!$bDeDupe) $sSQL .= ",place_id"; $sSQL .= ",get_address_by_language(place_id, $sLanguagePrefArraySQL) "; @@ -1143,15 +1195,15 @@ { // Get the bounding box and outline polygon $sSQL = "select place_id,numfeatures,area,outline,"; - $sSQL .= "ST_Y(ST_PointN(ExteriorRing(ST_Box2D(outline)),4)) as minlat,ST_Y(ST_PointN(ExteriorRing(ST_Box2D(outline)),2)) as maxlat,"; - $sSQL .= "ST_X(ST_PointN(ExteriorRing(ST_Box2D(outline)),1)) as minlon,ST_X(ST_PointN(ExteriorRing(ST_Box2D(outline)),3)) as maxlon,"; + $sSQL .= "ST_Y(ST_PointN(ST_ExteriorRing(Box2D(outline)),4)) as minlat,ST_Y(ST_PointN(ST_ExteriorRing(Box2D(outline)),2)) as maxlat,"; + $sSQL .= "ST_X(ST_PointN(ST_ExteriorRing(Box2D(outline)),1)) as minlon,ST_X(ST_PointN(ST_ExteriorRing(Box2D(outline)),3)) as maxlon,"; $sSQL .= "ST_AsText(outline) as outlinestring from get_place_boundingbox_quick(".$aResult['place_id'].")"; $sSQL = "select place_id,0 as numfeatures,st_area(geometry) as area,"; $sSQL .= "ST_Y(centroid) as centrelat,ST_X(centroid) as centrelon,"; - $sSQL .= "ST_Y(ST_PointN(ExteriorRing(ST_Box2D(geometry)),4)) as minlat,ST_Y(ST_PointN(ExteriorRing(ST_Box2D(geometry)),2)) as maxlat,"; - $sSQL .= "ST_X(ST_PointN(ExteriorRing(ST_Box2D(geometry)),1)) as minlon,ST_X(ST_PointN(ExteriorRing(ST_Box2D(geometry)),3)) as maxlon,"; - $sSQL .= "ST_AsText(geometry) as outlinestring from placex where place_id = ".$aResult['place_id'].' and st_geometrytype(ST_Box2D(geometry)) = \'ST_Polygon\''; + $sSQL .= "ST_Y(ST_PointN(ST_ExteriorRing(Box2D(geometry)),4)) as minlat,ST_Y(ST_PointN(ST_ExteriorRing(Box2D(geometry)),2)) as maxlat,"; + $sSQL .= "ST_X(ST_PointN(ST_ExteriorRing(Box2D(geometry)),1)) as minlon,ST_X(ST_PointN(ST_ExteriorRing(Box2D(geometry)),3)) as maxlon,"; + $sSQL .= "ST_AsText(geometry) as outlinestring from placex where place_id = ".$aResult['place_id'].' and st_geometrytype(Box2D(geometry)) = \'ST_Polygon\''; $aPointPolygon = $oDB->getRow($sSQL); if (PEAR::IsError($aPointPolygon)) { @@ -1168,6 +1220,10 @@ { preg_match_all('/(-?[0-9.]+) (-?[0-9.]+)/',$aMatch[1],$aPolyPoints,PREG_SET_ORDER); } + elseif (preg_match('#MULTIPOLYGON\\(\\(\\(([- 0-9.,]+)#',$aPointPolygon['outlinestring'],$aMatch)) + { + preg_match_all('/(-?[0-9.]+) (-?[0-9.]+)/',$aMatch[1],$aPolyPoints,PREG_SET_ORDER); + } elseif (preg_match('#POINT\\((-?[0-9.]+) (-?[0-9.]+)\\)#',$aPointPolygon['outlinestring'],$aMatch)) { $fRadius = 0.01; @@ -1266,7 +1322,7 @@ { if (stripos($sAddress, $sWord)!==false) $iCountWords++; } - $aResult['importance'] = $aResult['importance'] + $iCountWords; + $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 //if (CONST_Debug) var_dump($aResult['class'].':'.$aResult['type'].':'.$aResult['admin_level']); /* @@ -1311,10 +1367,10 @@ $bFirst = false; } if (!$bDeDupe || (!isset($aOSMIDDone[$aResult['osm_type'].$aResult['osm_id']]) - && !isset($aClassTypeNameDone[$aResult['osm_type'].$aResult['osm_class'].$aResult['name']]))) + && !isset($aClassTypeNameDone[$aResult['osm_type'].$aResult['class'].$aResult['name']]))) { $aOSMIDDone[$aResult['osm_type'].$aResult['osm_id']] = true; - $aClassTypeNameDone[$aResult['osm_type'].$aResult['osm_class'].$aResult['name']] = true; + $aClassTypeNameDone[$aResult['osm_type'].$aResult['class'].$aResult['name']] = true; $aSearchResults[] = $aResult; }