From: Sarah Hoffmann Date: Fri, 30 Sep 2016 19:51:00 +0000 (+0200) Subject: Merge remote-tracking branch 'upstream/master' into cmake-port X-Git-Tag: deploy~424 X-Git-Url: https://git.openstreetmap.org./nominatim.git/commitdiff_plain/dba5b2f86e3b91eade86a851932004a56b34134e?hp=--cc Merge remote-tracking branch 'upstream/master' into cmake-port --- dba5b2f86e3b91eade86a851932004a56b34134e diff --cc lib/Geocode.php index bdc8226e,6bc2c1e6..b6458725 --- a/lib/Geocode.php +++ b/lib/Geocode.php @@@ -1,1838 -1,1638 +1,1638 @@@ oDB =& $oDB; - } - - function setReverseInPlan($bReverse) - { - $this->bReverseInPlan = $bReverse; - } - - function setLanguagePreference($aLangPref) - { - $this->aLangPrefOrder = $aLangPref; - } - - function getIncludeAddressDetails() - { - return $this->bIncludeAddressDetails; - } - - function getIncludeExtraTags() - { - return $this->bIncludeExtraTags; - } - - function getIncludeNameDetails() - { - return $this->bIncludeNameDetails; - } - - function setIncludePolygonAsPoints($b = true) - { - $this->bIncludePolygonAsPoints = $b; - } - - function setIncludePolygonAsText($b = true) - { - $this->bIncludePolygonAsText = $b; - } - - function setIncludePolygonAsGeoJSON($b = true) - { - $this->bIncludePolygonAsGeoJSON = $b; - } - - function setIncludePolygonAsKML($b = true) - { - $this->bIncludePolygonAsKML = $b; - } - - function setIncludePolygonAsSVG($b = true) - { - $this->bIncludePolygonAsSVG = $b; - } - - function setPolygonSimplificationThreshold($f) - { - $this->fPolygonSimplificationThreshold = $f; - } - - function setLimit($iLimit = 10) - { - if ($iLimit > 50) $iLimit = 50; - if ($iLimit < 1) $iLimit = 1; - - $this->iFinalLimit = $iLimit; - $this->iLimit = $this->iFinalLimit + min($this->iFinalLimit, 10); - } - - function getExcludedPlaceIDs() - { - return $this->aExcludePlaceIDs; - } - - function setViewBox($fLeft, $fBottom, $fRight, $fTop) - { - $this->aViewBox = array($fLeft, $fBottom, $fRight, $fTop); - } - - function getViewBoxString() - { - if (!$this->aViewBox) return null; - return $this->aViewBox[0].','.$this->aViewBox[3].','.$this->aViewBox[2].','.$this->aViewBox[1]; - } - - function setFeatureType($sFeatureType) - { - switch($sFeatureType) - { - case 'country': - $this->setRankRange(4, 4); - break; - case 'state': - $this->setRankRange(8, 8); - break; - case 'city': - $this->setRankRange(14, 16); - break; - case 'settlement': - $this->setRankRange(8, 20); - break; - } - } - - function setRankRange($iMin, $iMax) - { - $this->iMinAddressRank = $iMin; - $this->iMaxAddressRank = $iMax; - } - - function setNearPoint($aNearPoint, $fRadiusDeg = 0.1) - { - $this->aNearPoint = array((float)$aNearPoint[0], (float)$aNearPoint[1], (float)$fRadiusDeg); - } - - function setQuery($sQueryString) - { - $this->sQuery = $sQueryString; - $this->aStructuredQuery = false; - } - - function getQueryString() - { - return $this->sQuery; - } - - - function loadParamArray($aParams) - { - if (isset($aParams['addressdetails'])) $this->bIncludeAddressDetails = (bool)$aParams['addressdetails']; - if (isset($aParams['extratags'])) $this->bIncludeExtraTags = (bool)$aParams['extratags']; - if (isset($aParams['namedetails'])) $this->bIncludeNameDetails = (bool)$aParams['namedetails']; - - if (isset($aParams['bounded'])) $this->bBoundedSearch = (bool)$aParams['bounded']; - if (isset($aParams['dedupe'])) $this->bDeDupe = (bool)$aParams['dedupe']; - - if (isset($aParams['limit'])) $this->setLimit((int)$aParams['limit']); - if (isset($aParams['offset'])) $this->iOffset = (int)$aParams['offset']; - - if (isset($aParams['fallback'])) $this->bFallback = (bool)$aParams['fallback']; - - // List of excluded Place IDs - used for more acurate pageing - if (isset($aParams['exclude_place_ids']) && $aParams['exclude_place_ids']) - { - foreach(explode(',',$aParams['exclude_place_ids']) as $iExcludedPlaceID) - { - $iExcludedPlaceID = (int)$iExcludedPlaceID; - if ($iExcludedPlaceID) - $aExcludePlaceIDs[$iExcludedPlaceID] = $iExcludedPlaceID; - } - - if (isset($aExcludePlaceIDs)) - $this->aExcludePlaceIDs = $aExcludePlaceIDs; - } - - // Only certain ranks of feature - if (isset($aParams['featureType'])) $this->setFeatureType($aParams['featureType']); - if (isset($aParams['featuretype'])) $this->setFeatureType($aParams['featuretype']); - - // Country code list - if (isset($aParams['countrycodes'])) - { - $aCountryCodes = array(); - foreach(explode(',',$aParams['countrycodes']) as $sCountryCode) - { - if (preg_match('/^[a-zA-Z][a-zA-Z]$/', $sCountryCode)) - { - $aCountryCodes[] = strtolower($sCountryCode); - } - } - $this->aCountryCodes = $aCountryCodes; - } - - if (isset($aParams['viewboxlbrt']) && $aParams['viewboxlbrt']) - { - $aCoOrdinatesLBRT = explode(',',$aParams['viewboxlbrt']); - $this->setViewBox($aCoOrdinatesLBRT[0], $aCoOrdinatesLBRT[1], $aCoOrdinatesLBRT[2], $aCoOrdinatesLBRT[3]); - } - else if (isset($aParams['viewbox']) && $aParams['viewbox']) - { - $aCoOrdinatesLTRB = explode(',',$aParams['viewbox']); - $this->setViewBox($aCoOrdinatesLTRB[0], $aCoOrdinatesLTRB[3], $aCoOrdinatesLTRB[2], $aCoOrdinatesLTRB[1]); - } - - if (isset($aParams['route']) && $aParams['route'] && isset($aParams['routewidth']) && $aParams['routewidth']) - { - $aPoints = explode(',',$aParams['route']); - if (sizeof($aPoints) % 2 != 0) - { - userError("Uneven number of points"); - exit; - } - $fPrevCoord = false; - $aRoute = array(); - foreach($aPoints as $i => $fPoint) - { - if ($i%2) - { - $aRoute[] = array((float)$fPoint, $fPrevCoord); - } - else - { - $fPrevCoord = (float)$fPoint; - } - } - $this->aRoutePoints = $aRoute; - } - } - - function setQueryFromParams($aParams) - { - // Search query - $sQuery = (isset($aParams['q'])?trim($aParams['q']):''); - if (!$sQuery) - { - $this->setStructuredQuery(@$aParams['amenity'], @$aParams['street'], @$aParams['city'], @$aParams['county'], @$aParams['state'], @$aParams['country'], @$aParams['postalcode']); - $this->setReverseInPlan(false); - } - else - { - $this->setQuery($sQuery); - } - } - - function loadStructuredAddressElement($sValue, $sKey, $iNewMinAddressRank, $iNewMaxAddressRank, $aItemListValues) - { - $sValue = trim($sValue); - if (!$sValue) return false; - $this->aStructuredQuery[$sKey] = $sValue; - if ($this->iMinAddressRank == 0 && $this->iMaxAddressRank == 30) - { - $this->iMinAddressRank = $iNewMinAddressRank; - $this->iMaxAddressRank = $iNewMaxAddressRank; - } - if ($aItemListValues) $this->aAddressRankList = array_merge($this->aAddressRankList, $aItemListValues); - return true; - } - - function setStructuredQuery($sAmentiy = false, $sStreet = false, $sCity = false, $sCounty = false, $sState = false, $sCountry = false, $sPostalCode = false) - { - $this->sQuery = false; - - // Reset - $this->iMinAddressRank = 0; - $this->iMaxAddressRank = 30; - $this->aAddressRankList = array(); - - $this->aStructuredQuery = array(); - $this->sAllowedTypesSQLList = ''; - - $this->loadStructuredAddressElement($sAmentiy, 'amenity', 26, 30, false); - $this->loadStructuredAddressElement($sStreet, 'street', 26, 30, false); - $this->loadStructuredAddressElement($sCity, 'city', 14, 24, false); - $this->loadStructuredAddressElement($sCounty, 'county', 9, 13, false); - $this->loadStructuredAddressElement($sState, 'state', 8, 8, false); - $this->loadStructuredAddressElement($sPostalCode, 'postalcode' , 5, 11, array(5, 11)); - $this->loadStructuredAddressElement($sCountry, 'country', 4, 4, false); - - if (sizeof($this->aStructuredQuery) > 0) - { - $this->sQuery = join(', ', $this->aStructuredQuery); - if ($this->iMaxAddressRank < 30) - { - $sAllowedTypesSQLList = '(\'place\',\'boundary\')'; - } - } - } - - function fallbackStructuredQuery() - { - if (!$this->aStructuredQuery) return false; - - $aParams = $this->aStructuredQuery; - - if (sizeof($aParams) == 1) return false; - - $aOrderToFallback = array('postalcode', 'street', 'city', 'county', 'state'); - - foreach($aOrderToFallback as $sType) - { - if (isset($aParams[$sType])) - { - unset($aParams[$sType]); - $this->setStructuredQuery(@$aParams['amenity'], @$aParams['street'], @$aParams['city'], @$aParams['county'], @$aParams['state'], @$aParams['country'], @$aParams['postalcode']); - return true; - } - } - - return false; - } - - function getDetails($aPlaceIDs) - { - //$aPlaceIDs is an array with key: placeID and value: tiger-housenumber, if found, else -1 - if (sizeof($aPlaceIDs) == 0) return array(); - - $sLanguagePrefArraySQL = "ARRAY[".join(',',array_map("getDBQuoted",$this->aLangPrefOrder))."]"; - - // Get the details for display (is this a redundant extra step?) - $sPlaceIDs = join(',', array_keys($aPlaceIDs)); - - $sImportanceSQL = ''; - if ($this->sViewboxSmallSQL) $sImportanceSQL .= " case when ST_Contains($this->sViewboxSmallSQL, ST_Collect(centroid)) THEN 1 ELSE 0.75 END * "; - if ($this->sViewboxLargeSQL) $sImportanceSQL .= " case when ST_Contains($this->sViewboxLargeSQL, ST_Collect(centroid)) THEN 1 ELSE 0.75 END * "; - - $sSQL = "select osm_type,osm_id,class,type,admin_level,rank_search,rank_address,min(place_id) as place_id, min(parent_place_id) as parent_place_id, calculated_country_code as country_code,"; - $sSQL .= "get_address_by_language(place_id, -1, $sLanguagePrefArraySQL) as langaddress,"; - $sSQL .= "get_name_by_language(name, $sLanguagePrefArraySQL) as placename,"; - $sSQL .= "get_name_by_language(name, ARRAY['ref']) as ref,"; - if ($this->bIncludeExtraTags) $sSQL .= "hstore_to_json(extratags)::text as extra,"; - if ($this->bIncludeNameDetails) $sSQL .= "hstore_to_json(name)::text as names,"; - $sSQL .= "avg(ST_X(centroid)) as lon,avg(ST_Y(centroid)) as lat, "; - $sSQL .= $sImportanceSQL."coalesce(importance,0.75-(rank_search::float/40)) as importance, "; - $sSQL .= "(select max(p.importance*(p.rank_address+2)) from place_addressline s, placex p where s.place_id = min(CASE WHEN placex.rank_search < 28 THEN placex.place_id ELSE placex.parent_place_id END) and p.place_id = s.address_place_id and s.isaddress and p.importance is not null) as addressimportance, "; - $sSQL .= "(extratags->'place') as extra_place "; - $sSQL .= "from placex where place_id in ($sPlaceIDs) "; - $sSQL .= "and (placex.rank_address between $this->iMinAddressRank and $this->iMaxAddressRank "; - if (14 >= $this->iMinAddressRank && 14 <= $this->iMaxAddressRank) $sSQL .= " OR (extratags->'place') = 'city'"; - if ($this->aAddressRankList) $sSQL .= " OR placex.rank_address in (".join(',',$this->aAddressRankList).")"; - $sSQL .= ") "; - if ($this->sAllowedTypesSQLList) $sSQL .= "and placex.class in $this->sAllowedTypesSQLList "; - $sSQL .= "and linked_place_id is null "; - $sSQL .= "group by osm_type,osm_id,class,type,admin_level,rank_search,rank_address,calculated_country_code,importance"; - if (!$this->bDeDupe) $sSQL .= ",place_id"; - $sSQL .= ",langaddress "; - $sSQL .= ",placename "; - $sSQL .= ",ref "; - if ($this->bIncludeExtraTags) $sSQL .= ",extratags"; - if ($this->bIncludeNameDetails) $sSQL .= ",name"; - $sSQL .= ",extratags->'place' "; - - if (30 >= $this->iMinAddressRank && 30 <= $this->iMaxAddressRank) - { - //only Tiger housenumbers and interpolation lines need to be interpolated, because they are saved as lines - // with start- and endnumber, the common osm housenumbers are usually saved as points - $sHousenumbers = ""; - $i = 0; - $length = count($aPlaceIDs); - foreach($aPlaceIDs as $placeID => $housenumber) - { - $i++; - $sHousenumbers .= "(".$placeID.", ".$housenumber.")"; - if($i<$length) - $sHousenumbers .= ", "; - } - if (CONST_Use_US_Tiger_Data) - { - //Tiger search only if a housenumber was searched and if it was found (i.e. aPlaceIDs[placeID] = housenumber != -1) (realized through a join) - $sSQL .= " union"; - $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, min(parent_place_id) as parent_place_id, 'us' as country_code"; - $sSQL .= ", get_address_by_language(place_id, housenumber_for_place, $sLanguagePrefArraySQL) as langaddress "; - $sSQL .= ", null as placename"; - $sSQL .= ", null as ref"; - if ($this->bIncludeExtraTags) $sSQL .= ", null as extra"; - if ($this->bIncludeNameDetails) $sSQL .= ", null as names"; - $sSQL .= ", avg(st_x(centroid)) as lon, avg(st_y(centroid)) as lat,"; - $sSQL .= $sImportanceSQL."-1.15 as importance "; - $sSQL .= ", (select max(p.importance*(p.rank_address+2)) from place_addressline s, placex p where s.place_id = min(blub.parent_place_id) and p.place_id = s.address_place_id and s.isaddress and p.importance is not null) as addressimportance "; - $sSQL .= ", null as extra_place "; - $sSQL .= " from (select place_id"; - //interpolate the Tiger housenumbers here - $sSQL .= ", ST_LineInterpolatePoint(linegeo, (housenumber_for_place-startnumber::float)/(endnumber-startnumber)::float) as centroid, parent_place_id, housenumber_for_place"; - $sSQL .= " from (location_property_tiger "; - $sSQL .= " join (values ".$sHousenumbers.") as housenumbers(place_id, housenumber_for_place) using(place_id)) "; - $sSQL .= " where housenumber_for_place>=0 and 30 between $this->iMinAddressRank and $this->iMaxAddressRank) as blub"; //postgres wants an alias here - $sSQL .= " group by place_id, housenumber_for_place"; //is this group by really needed?, place_id + housenumber (in combination) are unique - if (!$this->bDeDupe) $sSQL .= ", place_id "; - } - // osmline - // interpolation line search only if a housenumber was searched and if it was found (i.e. aPlaceIDs[placeID] = housenumber != -1) (realized through a join) - $sSQL .= " union "; - $sSQL .= "select 'W' 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, min(parent_place_id) as parent_place_id, calculated_country_code as country_code, "; - $sSQL .= "get_address_by_language(place_id, housenumber_for_place, $sLanguagePrefArraySQL) as langaddress, "; - $sSQL .= "null as placename, "; - $sSQL .= "null as ref, "; - if ($this->bIncludeExtraTags) $sSQL .= "null as extra, "; - if ($this->bIncludeNameDetails) $sSQL .= "null as names, "; - $sSQL .= " avg(st_x(centroid)) as lon, avg(st_y(centroid)) as lat,"; - $sSQL .= $sImportanceSQL."-0.1 as importance, "; // slightly smaller than the importance for normal houses with rank 30, which is 0 - $sSQL .= " (select max(p.importance*(p.rank_address+2)) from place_addressline s, placex p"; - $sSQL .= " where s.place_id = min(blub.parent_place_id) and p.place_id = s.address_place_id and s.isaddress and p.importance is not null) as addressimportance,"; - $sSQL .= " null as extra_place "; - $sSQL .= " from (select place_id, calculated_country_code "; - //interpolate the housenumbers here - $sSQL .= ", CASE WHEN startnumber != endnumber THEN ST_LineInterpolatePoint(linegeo, (housenumber_for_place-startnumber::float)/(endnumber-startnumber)::float) "; - $sSQL .= " ELSE ST_LineInterpolatePoint(linegeo, 0.5) END as centroid"; - $sSQL .= ", parent_place_id, housenumber_for_place "; - $sSQL .= " from (location_property_osmline "; - $sSQL .= " join (values ".$sHousenumbers.") as housenumbers(place_id, housenumber_for_place) using(place_id)) "; - $sSQL .= " where housenumber_for_place>=0 and 30 between $this->iMinAddressRank and $this->iMaxAddressRank) as blub"; //postgres wants an alias here - $sSQL .= " group by place_id, housenumber_for_place, calculated_country_code "; //is this group by really needed?, place_id + housenumber (in combination) are unique - if (!$this->bDeDupe) $sSQL .= ", place_id "; - - if (CONST_Use_Aux_Location_data) - { - $sSQL .= " union "; - $sSQL .= "select 'L' as osm_type, place_id as osm_id, 'place' as class, 'house' as type, null as admin_level, 0 as rank_search, 0 as rank_address, min(place_id) as place_id, min(parent_place_id) as parent_place_id, 'us' as country_code, "; - $sSQL .= "get_address_by_language(place_id, -1, $sLanguagePrefArraySQL) as langaddress, "; - $sSQL .= "null as placename, "; - $sSQL .= "null as ref, "; - if ($this->bIncludeExtraTags) $sSQL .= "null as extra, "; - if ($this->bIncludeNameDetails) $sSQL .= "null as names, "; - $sSQL .= "avg(ST_X(centroid)) as lon, avg(ST_Y(centroid)) as lat, "; - $sSQL .= $sImportanceSQL."-1.10 as importance, "; - $sSQL .= "(select max(p.importance*(p.rank_address+2)) from place_addressline s, placex p where s.place_id = min(location_property_aux.parent_place_id) and p.place_id = s.address_place_id and s.isaddress and p.importance is not null) as addressimportance, "; - $sSQL .= "null as extra_place "; - $sSQL .= "from location_property_aux where place_id in ($sPlaceIDs) "; - $sSQL .= "and 30 between $this->iMinAddressRank and $this->iMaxAddressRank "; - $sSQL .= "group by place_id"; - if (!$this->bDeDupe) $sSQL .= ", place_id"; - $sSQL .= ", get_address_by_language(place_id, -1, $sLanguagePrefArraySQL) "; - } - } - - $sSQL .= " order by importance desc"; - if (CONST_Debug) { echo "
"; var_dump($sSQL); } - $aSearchResults = chksql($this->oDB->getAll($sSQL), - "Could not get details for place."); - - return $aSearchResults; - } - - function getGroupedSearches($aSearches, $aPhraseTypes, $aPhrases, $aValidTokens, $aWordFrequencyScores, $bStructuredPhrases) - { - /* - 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) - { - // Too many permutations - too expensive - if ($iWordSet > 120) break; - - $aWordsetSearches = $aSearches; - - // Add all words from this wordset - foreach($aWordset as $iToken => $sToken) - { - //echo "
$sToken"; - $aNewWordsetSearches = array(); - - foreach($aWordsetSearches as $aCurrentSearch) - { - //echo ""; - //var_dump($aCurrentSearch); - //echo ""; - - // If the token is valid - if (isset($aValidTokens[' '.$sToken])) - { - foreach($aValidTokens[' '.$sToken] as $aSearchTerm) - { - $aSearch = $aCurrentSearch; - $aSearch['iSearchRank']++; - if (($sPhraseType == '' || $sPhraseType == 'country') && !empty($aSearchTerm['country_code']) && $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 (optimisation) - if (($iToken+1 != sizeof($aWordset) || $iPhrase+1 != sizeof($aPhrases))) - { - $aSearch['iSearchRank'] += 5; - } - if ($aSearch['iSearchRank'] < $this->iMaxRank) $aNewWordsetSearches[] = $aSearch; - } - } - elseif (isset($aSearchTerm['lat']) && $aSearchTerm['lat'] !== '' && $aSearchTerm['lat'] !== null) - { - if ($aSearch['fLat'] === '') - { - $aSearch['fLat'] = $aSearchTerm['lat']; - $aSearch['fLon'] = $aSearchTerm['lon']; - $aSearch['fRadius'] = $aSearchTerm['radius']; - if ($aSearch['iSearchRank'] < $this->iMaxRank) $aNewWordsetSearches[] = $aSearch; - } - } - elseif ($sPhraseType == 'postalcode') - { - // 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 - if (isset($aSearchTerm['word_id']) && $aSearchTerm['word_id']) - { - // If we already have a name try putting the postcode first - if (sizeof($aSearch['aName'])) - { - $aNewSearch = $aSearch; - $aNewSearch['aAddress'] = array_merge($aNewSearch['aAddress'], $aNewSearch['aName']); - $aNewSearch['aName'] = array(); - $aNewSearch['aName'][$aSearchTerm['word_id']] = $aSearchTerm['word_id']; - if ($aSearch['iSearchRank'] < $this->iMaxRank) $aNewWordsetSearches[] = $aNewSearch; - } - - if (sizeof($aSearch['aName'])) - { - if ((!$bStructuredPhrases || $iPhrase > 0) && $sPhraseType != 'country' && (!isset($aValidTokens[$sToken]) || strpos($sToken, ' ') !== false)) - { - $aSearch['aAddress'][$aSearchTerm['word_id']] = $aSearchTerm['word_id']; - } - else - { - $aCurrentSearch['aFullNameAddress'][$aSearchTerm['word_id']] = $aSearchTerm['word_id']; - $aSearch['iSearchRank'] += 1000; // skip; - } - } - else - { - $aSearch['aName'][$aSearchTerm['word_id']] = $aSearchTerm['word_id']; - //$aSearch['iNamePhrase'] = $iPhrase; - } - if ($aSearch['iSearchRank'] < $this->iMaxRank) $aNewWordsetSearches[] = $aSearch; - } - - } - elseif (($sPhraseType == '' || $sPhraseType == 'street') && $aSearchTerm['class'] == 'place' && $aSearchTerm['type'] == 'house') - { - if ($aSearch['sHouseNumber'] === '') - { - $aSearch['sHouseNumber'] = $sToken; - // sanity check: if the housenumber is not mainly made - // up of numbers, add a penalty - if (preg_match_all("/[^0-9]/", $sToken, $aMatches) > 2) $aSearch['iSearchRank']++; - // also housenumbers should appear in the first or second phrase - if ($iPhrase > 1) $aSearch['iSearchRank'] += 1; - if ($aSearch['iSearchRank'] < $this->iMaxRank) $aNewWordsetSearches[] = $aSearch; - /* - // Fall back to not searching for this item (better than nothing) - $aSearch = $aCurrentSearch; - $aSearch['iSearchRank'] += 1; - if ($aSearch['iSearchRank'] < $this->iMaxRank) $aNewWordsetSearches[] = $aSearch; - */ - } - } - elseif ($sPhraseType == '' && $aSearchTerm['class'] !== '' && $aSearchTerm['class'] !== null) - { - if ($aSearch['sClass'] === '') - { - $aSearch['sOperator'] = $aSearchTerm['operator']; - $aSearch['sClass'] = $aSearchTerm['class']; - $aSearch['sType'] = $aSearchTerm['type']; - if (sizeof($aSearch['aName'])) $aSearch['sOperator'] = 'name'; - else $aSearch['sOperator'] = 'near'; // near = in for the moment - if (strlen($aSearchTerm['operator']) == 0) $aSearch['iSearchRank'] += 1; - - if ($aSearch['iSearchRank'] < $this->iMaxRank) $aNewWordsetSearches[] = $aSearch; - } - } - elseif (isset($aSearchTerm['word_id']) && $aSearchTerm['word_id']) - { - if (sizeof($aSearch['aName'])) - { - if ((!$bStructuredPhrases || $iPhrase > 0) && $sPhraseType != 'country' && (!isset($aValidTokens[$sToken]) || strpos($sToken, ' ') !== false)) - { - $aSearch['aAddress'][$aSearchTerm['word_id']] = $aSearchTerm['word_id']; - } - else - { - $aCurrentSearch['aFullNameAddress'][$aSearchTerm['word_id']] = $aSearchTerm['word_id']; - $aSearch['iSearchRank'] += 1000; // skip; - } - } - else - { - $aSearch['aName'][$aSearchTerm['word_id']] = $aSearchTerm['word_id']; - //$aSearch['iNamePhrase'] = $iPhrase; - } - if ($aSearch['iSearchRank'] < $this->iMaxRank) $aNewWordsetSearches[] = $aSearch; - } - } - } - // Look for partial matches. - // Note that there is no point in adding country terms here - // because country are omitted in the address. - if (isset($aValidTokens[$sToken]) && $sPhraseType != 'country') - { - // Allow searching for a word - but at extra cost - foreach($aValidTokens[$sToken] as $aSearchTerm) - { - if (isset($aSearchTerm['word_id']) && $aSearchTerm['word_id']) - { - if ((!$bStructuredPhrases || $iPhrase > 0) && sizeof($aCurrentSearch['aName']) && strpos($sToken, ' ') === false) - { - $aSearch = $aCurrentSearch; - $aSearch['iSearchRank'] += 1; - if ($aWordFrequencyScores[$aSearchTerm['word_id']] < CONST_Max_Word_Frequency) - { - $aSearch['aAddress'][$aSearchTerm['word_id']] = $aSearchTerm['word_id']; - if ($aSearch['iSearchRank'] < $this->iMaxRank) $aNewWordsetSearches[] = $aSearch; - } - elseif (isset($aValidTokens[' '.$sToken])) // revert to the token version? - { - $aSearch['aAddressNonSearch'][$aSearchTerm['word_id']] = $aSearchTerm['word_id']; - $aSearch['iSearchRank'] += 1; - if ($aSearch['iSearchRank'] < $this->iMaxRank) $aNewWordsetSearches[] = $aSearch; - foreach($aValidTokens[' '.$sToken] as $aSearchTermToken) - { - if (empty($aSearchTermToken['country_code']) - && empty($aSearchTermToken['lat']) - && empty($aSearchTermToken['class'])) - { - $aSearch = $aCurrentSearch; - $aSearch['iSearchRank'] += 1; - $aSearch['aAddress'][$aSearchTermToken['word_id']] = $aSearchTermToken['word_id']; - if ($aSearch['iSearchRank'] < $this->iMaxRank) $aNewWordsetSearches[] = $aSearch; - } - } - } - else - { - $aSearch['aAddressNonSearch'][$aSearchTerm['word_id']] = $aSearchTerm['word_id']; - if (preg_match('#^[0-9]+$#', $sToken)) $aSearch['iSearchRank'] += 2; - if ($aSearch['iSearchRank'] < $this->iMaxRank) $aNewWordsetSearches[] = $aSearch; - } - } - - if (!sizeof($aCurrentSearch['aName']) || $aCurrentSearch['iNamePhrase'] == $iPhrase) - { - $aSearch = $aCurrentSearch; - $aSearch['iSearchRank'] += 1; - if (!sizeof($aCurrentSearch['aName'])) $aSearch['iSearchRank'] += 1; - if (preg_match('#^[0-9]+$#', $sToken)) $aSearch['iSearchRank'] += 2; - if ($aWordFrequencyScores[$aSearchTerm['word_id']] < CONST_Max_Word_Frequency) - $aSearch['aName'][$aSearchTerm['word_id']] = $aSearchTerm['word_id']; - else - $aSearch['aNameNonSearch'][$aSearchTerm['word_id']] = $aSearchTerm['word_id']; - $aSearch['iNamePhrase'] = $iPhrase; - if ($aSearch['iSearchRank'] < $this->iMaxRank) $aNewWordsetSearches[] = $aSearch; - } - } - } - } - else - { - // Allow skipping a word - but at EXTREAM cost - //$aSearch = $aCurrentSearch; - //$aSearch['iSearchRank']+=100; - //$aNewWordsetSearches[] = $aSearch; - } - } - // Sort and cut - usort($aNewWordsetSearches, 'bySearchRank'); - $aWordsetSearches = array_slice($aNewWordsetSearches, 0, 50); - } - //var_Dump('
',sizeof($aWordsetSearches)); exit; - - $aNewPhraseSearches = array_merge($aNewPhraseSearches, $aNewWordsetSearches); - usort($aNewPhraseSearches, 'bySearchRank'); - - $aSearchHash = array(); - foreach($aNewPhraseSearches as $iSearch => $aSearch) - { - $sHash = serialize($aSearch); - if (isset($aSearchHash[$sHash])) unset($aNewPhraseSearches[$iSearch]); - else $aSearchHash[$sHash] = 1; - } - - $aNewPhraseSearches = array_slice($aNewPhraseSearches, 0, 50); - } - - // Re-group the searches by their score, junk anything over 20 as just not worth trying - $aGroupedSearches = array(); - foreach($aNewPhraseSearches as $aSearch) - { - if ($aSearch['iSearchRank'] < $this->iMaxRank) - { - if (!isset($aGroupedSearches[$aSearch['iSearchRank']])) $aGroupedSearches[$aSearch['iSearchRank']] = array(); - $aGroupedSearches[$aSearch['iSearchRank']][] = $aSearch; - } - } - ksort($aGroupedSearches); - - $iSearchCount = 0; - $aSearches = array(); - foreach($aGroupedSearches as $iScore => $aNewSearches) - { - $iSearchCount += sizeof($aNewSearches); - $aSearches = array_merge($aSearches, $aNewSearches); - if ($iSearchCount > 50) break; - } - - //if (CONST_Debug) _debugDumpGroupedSearches($aGroupedSearches, $aValidTokens); - - } - return $aGroupedSearches; - - } - - /* Perform the actual query lookup. - - Returns an ordered list of results, each with the following fields: - osm_type: type of corresponding OSM object - N - node - W - way - R - relation - P - postcode (internally computed) - osm_id: id of corresponding OSM object - class: general object class (corresponds to tag key of primary OSM tag) - type: subclass of object (corresponds to tag value of primary OSM tag) - admin_level: see http://wiki.openstreetmap.org/wiki/Admin_level - rank_search: rank in search hierarchy - (see also http://wiki.openstreetmap.org/wiki/Nominatim/Development_overview#Country_to_street_level) - rank_address: rank in address hierarchy (determines orer in address) - place_id: internal key (may differ between different instances) - country_code: ISO country code - langaddress: localized full address - placename: localized name of object - ref: content of ref tag (if available) - lon: longitude - lat: latitude - importance: importance of place based on Wikipedia link count - addressimportance: cumulated importance of address elements - extra_place: type of place (for admin boundaries, if there is a place tag) - aBoundingBox: bounding Box - label: short description of the object class/type (English only) - name: full name (currently the same as langaddress) - foundorder: secondary ordering for places with same importance - */ - function lookup() - { - if (!$this->sQuery && !$this->aStructuredQuery) return false; - - $sLanguagePrefArraySQL = "ARRAY[".join(',',array_map("getDBQuoted",$this->aLangPrefOrder))."]"; - $sCountryCodesSQL = false; - if ($this->aCountryCodes && sizeof($this->aCountryCodes)) - { - $sCountryCodesSQL = join(',', array_map('addQuotes', $this->aCountryCodes)); - } - - $sQuery = $this->sQuery; - - // Conflicts between US state abreviations and various words for 'the' in different languages - if (isset($this->aLangPrefOrder['name:en'])) - { - $sQuery = preg_replace('/(^|,)\s*il\s*(,|$)/','\1illinois\2', $sQuery); - $sQuery = preg_replace('/(^|,)\s*al\s*(,|$)/','\1alabama\2', $sQuery); - $sQuery = preg_replace('/(^|,)\s*la\s*(,|$)/','\1louisiana\2', $sQuery); - } - - // View Box SQL - $sViewboxCentreSQL = false; - $bBoundingBoxSearch = false; - if ($this->aViewBox) - { - $fHeight = $this->aViewBox[0]-$this->aViewBox[2]; - $fWidth = $this->aViewBox[1]-$this->aViewBox[3]; - $aBigViewBox[0] = $this->aViewBox[0] + $fHeight; - $aBigViewBox[2] = $this->aViewBox[2] - $fHeight; - $aBigViewBox[1] = $this->aViewBox[1] + $fWidth; - $aBigViewBox[3] = $this->aViewBox[3] - $fWidth; - - $this->sViewboxSmallSQL = "ST_SetSRID(ST_MakeBox2D(ST_Point(".(float)$this->aViewBox[0].",".(float)$this->aViewBox[1]."),ST_Point(".(float)$this->aViewBox[2].",".(float)$this->aViewBox[3].")),4326)"; - $this->sViewboxLargeSQL = "ST_SetSRID(ST_MakeBox2D(ST_Point(".(float)$aBigViewBox[0].",".(float)$aBigViewBox[1]."),ST_Point(".(float)$aBigViewBox[2].",".(float)$aBigViewBox[3].")),4326)"; - $bBoundingBoxSearch = $this->bBoundedSearch; - } - - // Route SQL - if ($this->aRoutePoints) - { - $sViewboxCentreSQL = "ST_SetSRID('LINESTRING("; - $bFirst = true; - foreach($this->aRoutePoints as $aPoint) - { - if (!$bFirst) $sViewboxCentreSQL .= ","; - $sViewboxCentreSQL .= $aPoint[0].' '.$aPoint[1]; - $bFirst = false; - } - $sViewboxCentreSQL .= ")'::geometry,4326)"; - - $sSQL = "select st_buffer(".$sViewboxCentreSQL.",".(float)($_GET['routewidth']/69).")"; - $this->sViewboxSmallSQL = chksql($this->oDB->getOne($sSQL), - "Could not get small viewbox."); - $this->sViewboxSmallSQL = "'".$this->sViewboxSmallSQL."'::geometry"; - - $sSQL = "select st_buffer(".$sViewboxCentreSQL.",".(float)($_GET['routewidth']/30).")"; - $this->sViewboxLargeSQL = chksql($this->oDB->getOne($sSQL), - "Could not get large viewbox."); - $this->sViewboxLargeSQL = "'".$this->sViewboxLargeSQL."'::geometry"; - $bBoundingBoxSearch = $this->bBoundedSearch; - } - - // Do we have anything that looks like a lat/lon pair? - if ( $aLooksLike = looksLikeLatLonPair($sQuery) ) - { - $this->setNearPoint(array($aLooksLike['lat'], $aLooksLike['lon'])); - $sQuery = $aLooksLike['query']; - } - - $aSearchResults = array(); - if ($sQuery || $this->aStructuredQuery) - { - // Start with a blank search - $aSearches = array( - array('iSearchRank' => 0, - 'iNamePhrase' => -1, - 'sCountryCode' => false, - 'aName' => array(), - 'aAddress' => array(), - 'aFullNameAddress' => array(), - 'aNameNonSearch' => array(), - 'aAddressNonSearch' => array(), - 'sOperator' => '', - 'aFeatureName' => array(), - 'sClass' => '', - 'sType' => '', - 'sHouseNumber' => '', - 'fLat' => '', - 'fLon' => '', - 'fRadius' => '' - ) - ); - - // Do we have a radius search? - $sNearPointSQL = false; - if ($this->aNearPoint) - { - $sNearPointSQL = "ST_SetSRID(ST_Point(".(float)$this->aNearPoint[1].",".(float)$this->aNearPoint[0]."),4326)"; - $aSearches[0]['fLat'] = (float)$this->aNearPoint[0]; - $aSearches[0]['fLon'] = (float)$this->aNearPoint[1]; - $aSearches[0]['fRadius'] = (float)$this->aNearPoint[2]; - } - - // Any 'special' terms in the search? - $bSpecialTerms = false; - preg_match_all('/\\[(.*)=(.*)\\]/', $sQuery, $aSpecialTermsRaw, PREG_SET_ORDER); - $aSpecialTerms = array(); - foreach($aSpecialTermsRaw as $aSpecialTerm) - { - $sQuery = str_replace($aSpecialTerm[0], ' ', $sQuery); - $aSpecialTerms[strtolower($aSpecialTerm[1])] = $aSpecialTerm[2]; - } - - preg_match_all('/\\[([\\w ]*)\\]/u', $sQuery, $aSpecialTermsRaw, PREG_SET_ORDER); - $aSpecialTerms = array(); - if (isset($this->aStructuredQuery['amenity']) && $this->aStructuredQuery['amenity']) - { - $aSpecialTermsRaw[] = array('['.$this->aStructuredQuery['amenity'].']', $this->aStructuredQuery['amenity']); - unset($this->aStructuredQuery['amenity']); - } - foreach($aSpecialTermsRaw as $aSpecialTerm) - { - $sQuery = str_replace($aSpecialTerm[0], ' ', $sQuery); - $sToken = chksql($this->oDB->getOne("select make_standard_name('".$aSpecialTerm[1]."') as string")); - $sSQL = 'select * from (select word_id,word_token, word, class, type, country_code, operator'; - $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 = chksql($this->oDB->getAll($sSQL)); - $aNewSearches = array(); - foreach($aSearches as $aSearch) - { - foreach($aSearchWords as $aSearchTerm) - { - $aNewSearch = $aSearch; - if ($aSearchTerm['country_code']) - { - $aNewSearch['sCountryCode'] = strtolower($aSearchTerm['country_code']); - $aNewSearches[] = $aNewSearch; - $bSpecialTerms = true; - } - if ($aSearchTerm['class']) - { - $aNewSearch['sClass'] = $aSearchTerm['class']; - $aNewSearch['sType'] = $aSearchTerm['type']; - $aNewSearches[] = $aNewSearch; - $bSpecialTerms = true; - } - } - } - $aSearches = $aNewSearches; - } - - // Split query into phrases - // Commas are used to reduce the search space by indicating where phrases split - if ($this->aStructuredQuery) - { - $aPhrases = $this->aStructuredQuery; - $bStructuredPhrases = true; - } - else - { - $aPhrases = explode(',',$sQuery); - $bStructuredPhrases = false; - } - - // Convert each phrase to standard form - // Create a list of standard words - // Get all 'sets' of words - // Generate a complete list of all - $aTokens = array(); - foreach($aPhrases as $iPhrase => $sPhrase) - { - $aPhrase = chksql($this->oDB->getRow("select make_standard_name('".pg_escape_string($sPhrase)."') as string"), - "Cannot nomralize query string (is it an UTF-8 string?)"); - if (trim($aPhrase['string'])) - { - $aPhrases[$iPhrase] = $aPhrase; - $aPhrases[$iPhrase]['words'] = explode(' ',$aPhrases[$iPhrase]['string']); - $aPhrases[$iPhrase]['wordsets'] = getWordSets($aPhrases[$iPhrase]['words'], 0); - $aTokens = array_merge($aTokens, getTokensFromSets($aPhrases[$iPhrase]['wordsets'])); - } - else - { - unset($aPhrases[$iPhrase]); - } - } - - // Reindex phrases - we make assumptions later on that they are numerically keyed in order - $aPhraseTypes = array_keys($aPhrases); - $aPhrases = array_values($aPhrases); - - if (sizeof($aTokens)) - { - // Check which tokens we have, get the ID numbers - $sSQL = 'select word_id,word_token, word, class, type, country_code, operator, search_name_count'; - $sSQL .= ' from word where word_token in ('.join(',',array_map("getDBQuoted",$aTokens)).')'; - - if (CONST_Debug) var_Dump($sSQL); - - $aValidTokens = array(); - if (sizeof($aTokens)) - { - $aDatabaseWords = chksql($this->oDB->getAll($sSQL), - "Could not get word tokens."); - } - else - { - $aDatabaseWords = array(); - } - $aPossibleMainWordIDs = array(); - $aWordFrequencyScores = array(); - foreach($aDatabaseWords as $aToken) - { - // Very special case - require 2 letter country param to match the country code found - if ($bStructuredPhrases && $aToken['country_code'] && !empty($this->aStructuredQuery['country']) - && strlen($this->aStructuredQuery['country']) == 2 && strtolower($this->aStructuredQuery['country']) != $aToken['country_code']) - { - continue; - } - - if (isset($aValidTokens[$aToken['word_token']])) - { - $aValidTokens[$aToken['word_token']][] = $aToken; - } - else - { - $aValidTokens[$aToken['word_token']] = array($aToken); - } - if (!$aToken['class'] && !$aToken['country_code']) $aPossibleMainWordIDs[$aToken['word_id']] = 1; - $aWordFrequencyScores[$aToken['word_id']] = $aToken['search_name_count'] + 1; - } - if (CONST_Debug) var_Dump($aPhrases, $aValidTokens); - - // Try and calculate GB postcodes we might be missing - foreach($aTokens as $sToken) - { - // Source of gb postcodes is now definitive - always use - if (preg_match('/^([A-Z][A-Z]?[0-9][0-9A-Z]? ?[0-9])([A-Z][A-Z])$/', strtoupper(trim($sToken)), $aData)) - { - if (substr($aData[1],-2,1) != ' ') - { - $aData[0] = substr($aData[0],0,strlen($aData[1])-1).' '.substr($aData[0],strlen($aData[1])-1); - $aData[1] = substr($aData[1],0,-1).' '.substr($aData[1],-1,1); - } - $aGBPostcodeLocation = gbPostcodeCalculate($aData[0], $aData[1], $aData[2], $this->oDB); - if ($aGBPostcodeLocation) - { - $aValidTokens[$sToken] = $aGBPostcodeLocation; - } - } - // US ZIP+4 codes - if there is no token, - // merge in the 5-digit ZIP code - else if (!isset($aValidTokens[$sToken]) && preg_match('/^([0-9]{5}) [0-9]{4}$/', $sToken, $aData)) - { - if (isset($aValidTokens[$aData[1]])) - { - foreach($aValidTokens[$aData[1]] as $aToken) - { - if (!$aToken['class']) - { - if (isset($aValidTokens[$sToken])) - { - $aValidTokens[$sToken][] = $aToken; - } - else - { - $aValidTokens[$sToken] = array($aToken); - } - } - } - } - } - } - - foreach($aTokens as $sToken) - { - // Unknown single word token with a number - assume it is a house number - if (!isset($aValidTokens[' '.$sToken]) && strpos($sToken,' ') === false && preg_match('/[0-9]/', $sToken)) - { - $aValidTokens[' '.$sToken] = array(array('class'=>'place','type'=>'house')); - } - } - - // Any words that have failed completely? - // TODO: suggestions - - // Start the search process - // array with: placeid => -1 | tiger-housenumber - $aResultPlaceIDs = array(); - - $aGroupedSearches = $this->getGroupedSearches($aSearches, $aPhraseTypes, $aPhrases, $aValidTokens, $aWordFrequencyScores, $bStructuredPhrases); - - if ($this->bReverseInPlan) - { - // Reverse phrase array and also reverse the order of the wordsets in - // the first and final phrase. Don't bother about phrases in the middle - // because order in the address doesn't matter. - $aPhrases = array_reverse($aPhrases); - $aPhrases[0]['wordsets'] = getInverseWordSets($aPhrases[0]['words'], 0); - if (sizeof($aPhrases) > 1) - { - $aFinalPhrase = end($aPhrases); - $aPhrases[sizeof($aPhrases)-1]['wordsets'] = getInverseWordSets($aFinalPhrase['words'], 0); - } - $aReverseGroupedSearches = $this->getGroupedSearches($aSearches, null, $aPhrases, $aValidTokens, $aWordFrequencyScores, false); - - foreach($aGroupedSearches as $aSearches) - { - foreach($aSearches as $aSearch) - { - if ($aSearch['iSearchRank'] < $this->iMaxRank) - { - if (!isset($aReverseGroupedSearches[$aSearch['iSearchRank']])) $aReverseGroupedSearches[$aSearch['iSearchRank']] = array(); - $aReverseGroupedSearches[$aSearch['iSearchRank']][] = $aSearch; - } - - } - } - - $aGroupedSearches = $aReverseGroupedSearches; - ksort($aGroupedSearches); - } - } - else - { - // Re-group the searches by their score, junk anything over 20 as just not worth trying - $aGroupedSearches = array(); - foreach($aSearches as $aSearch) - { - if ($aSearch['iSearchRank'] < $this->iMaxRank) - { - if (!isset($aGroupedSearches[$aSearch['iSearchRank']])) $aGroupedSearches[$aSearch['iSearchRank']] = array(); - $aGroupedSearches[$aSearch['iSearchRank']][] = $aSearch; - } - } - ksort($aGroupedSearches); - } - - if (CONST_Debug) var_Dump($aGroupedSearches); - - if (CONST_Search_TryDroppedAddressTerms && sizeof($this->aStructuredQuery) > 0) - { - $aCopyGroupedSearches = $aGroupedSearches; - foreach($aCopyGroupedSearches as $iGroup => $aSearches) - { - foreach($aSearches as $iSearch => $aSearch) - { - $aReductionsList = array($aSearch['aAddress']); - $iSearchRank = $aSearch['iSearchRank']; - while(sizeof($aReductionsList) > 0) - { - $iSearchRank += 5; - if ($iSearchRank > iMaxRank) break 3; - $aNewReductionsList = array(); - foreach($aReductionsList as $aReductionsWordList) - { - for ($iReductionWord = 0; $iReductionWord < sizeof($aReductionsWordList); $iReductionWord++) - { - $aReductionsWordListResult = array_merge(array_slice($aReductionsWordList, 0, $iReductionWord), array_slice($aReductionsWordList, $iReductionWord+1)); - $aReverseSearch = $aSearch; - $aSearch['aAddress'] = $aReductionsWordListResult; - $aSearch['iSearchRank'] = $iSearchRank; - $aGroupedSearches[$iSearchRank][] = $aReverseSearch; - if (sizeof($aReductionsWordListResult) > 0) - { - $aNewReductionsList[] = $aReductionsWordListResult; - } - } - } - $aReductionsList = $aNewReductionsList; - } - } - } - ksort($aGroupedSearches); - } - - // Filter out duplicate searches - $aSearchHash = array(); - foreach($aGroupedSearches as $iGroup => $aSearches) - { - foreach($aSearches as $iSearch => $aSearch) - { - $sHash = serialize($aSearch); - if (isset($aSearchHash[$sHash])) - { - unset($aGroupedSearches[$iGroup][$iSearch]); - if (sizeof($aGroupedSearches[$iGroup]) == 0) unset($aGroupedSearches[$iGroup]); - } - else - { - $aSearchHash[$sHash] = 1; - } - } - } - - if (CONST_Debug) _debugDumpGroupedSearches($aGroupedSearches, $aValidTokens); - - $iGroupLoop = 0; - $iQueryLoop = 0; - foreach($aGroupedSearches as $iGroupedRank => $aSearches) - { - $iGroupLoop++; - foreach($aSearches as $aSearch) - { - $iQueryLoop++; - $searchedHousenumber = -1; - - if (CONST_Debug) { echo "
Search Loop, group $iGroupLoop, loop $iQueryLoop"; } - if (CONST_Debug) _debugDumpGroupedSearches(array($iGroupedRank => array($aSearch)), $aValidTokens); - - // No location term? - if (!sizeof($aSearch['aName']) && !sizeof($aSearch['aAddress']) && !$aSearch['fLon']) - { - if ($aSearch['sCountryCode'] && !$aSearch['sClass'] && !$aSearch['sHouseNumber']) - { - // Just looking for a country by code - look it up - if (4 >= $this->iMinAddressRank && 4 <= $this->iMaxAddressRank) - { - $sSQL = "select place_id from placex where calculated_country_code='".$aSearch['sCountryCode']."' and rank_search = 4"; - if ($sCountryCodesSQL) $sSQL .= " and calculated_country_code in ($sCountryCodesSQL)"; - if ($bBoundingBoxSearch) - $sSQL .= " and _st_intersects($this->sViewboxSmallSQL, geometry)"; - $sSQL .= " order by st_area(geometry) desc limit 1"; - if (CONST_Debug) var_dump($sSQL); - $aPlaceIDs = chksql($this->oDB->getCol($sSQL)); - } - else - { - $aPlaceIDs = array(); - } - } - else - { - if (!$bBoundingBoxSearch && !$aSearch['fLon']) continue; - if (!$aSearch['sClass']) continue; - $sSQL = "select count(*) from pg_tables where tablename = 'place_classtype_".$aSearch['sClass']."_".$aSearch['sType']."'"; - if (chksql($this->oDB->getOne($sSQL))) - { - $sSQL = "select place_id from place_classtype_".$aSearch['sClass']."_".$aSearch['sType']." ct"; - if ($sCountryCodesSQL) $sSQL .= " join placex using (place_id)"; - $sSQL .= " where st_contains($this->sViewboxSmallSQL, ct.centroid)"; - if ($sCountryCodesSQL) $sSQL .= " and calculated_country_code in ($sCountryCodesSQL)"; - if (sizeof($this->aExcludePlaceIDs)) - { - $sSQL .= " and place_id not in (".join(',',$this->aExcludePlaceIDs).")"; - } - if ($sViewboxCentreSQL) $sSQL .= " order by st_distance($sViewboxCentreSQL, ct.centroid) asc"; - $sSQL .= " limit $this->iLimit"; - if (CONST_Debug) var_dump($sSQL); - $aPlaceIDs = chksql($this->oDB->getCol($sSQL)); - - // If excluded place IDs are given, it is fair to assume that - // there have been results in the small box, so no further - // expansion in that case. - // Also don't expand if bounded results were requested. - if (!sizeof($aPlaceIDs) && !sizeof($this->aExcludePlaceIDs) && !$this->bBoundedSearch) - { - $sSQL = "select place_id from place_classtype_".$aSearch['sClass']."_".$aSearch['sType']." ct"; - if ($sCountryCodesSQL) $sSQL .= " join placex using (place_id)"; - $sSQL .= " where st_contains($this->sViewboxLargeSQL, ct.centroid)"; - if ($sCountryCodesSQL) $sSQL .= " and calculated_country_code in ($sCountryCodesSQL)"; - if ($sViewboxCentreSQL) $sSQL .= " order by st_distance($sViewboxCentreSQL, ct.centroid) asc"; - $sSQL .= " limit $this->iLimit"; - if (CONST_Debug) var_dump($sSQL); - $aPlaceIDs = chksql($this->oDB->getCol($sSQL)); - } - } - else - { - $sSQL = "select place_id from placex where class='".$aSearch['sClass']."' and type='".$aSearch['sType']."'"; - $sSQL .= " and st_contains($this->sViewboxSmallSQL, geometry) and linked_place_id is null"; - if ($sCountryCodesSQL) $sSQL .= " and calculated_country_code in ($sCountryCodesSQL)"; - if ($sViewboxCentreSQL) $sSQL .= " order by st_distance($sViewboxCentreSQL, centroid) asc"; - $sSQL .= " limit $this->iLimit"; - if (CONST_Debug) var_dump($sSQL); - $aPlaceIDs = chksql($this->oDB->getCol($sSQL)); - } - } - } - // If a coordinate is given, the search must either - // be for a name or a special search. Ignore everythin else. - else if ($aSearch['fLon'] && !sizeof($aSearch['aName']) && !sizeof($aSearch['aAddress']) && !$aSearch['sClass']) - { - $aPlaceIDs = array(); - } - else - { - $aPlaceIDs = array(); - - // First we need a position, either aName or fLat or both - $aTerms = array(); - $aOrder = array(); - - if ($aSearch['sHouseNumber'] && sizeof($aSearch['aAddress'])) - { - $sHouseNumberRegex = '\\\\m'.$aSearch['sHouseNumber'].'\\\\M'; - $aOrder[] = ""; - $aOrder[0] = " (exists(select place_id from placex where parent_place_id = search_name.place_id"; - $aOrder[0] .= " and transliteration(housenumber) ~* E'".$sHouseNumberRegex."' limit 1) "; - // also housenumbers from interpolation lines table are needed - $aOrder[0] .= " or exists(select place_id from location_property_osmline where parent_place_id = search_name.place_id"; - $aOrder[0] .= " and ".intval($aSearch['sHouseNumber']).">=startnumber and ".intval($aSearch['sHouseNumber'])."<=endnumber limit 1))"; - $aOrder[0] .= " desc"; - } - - // TODO: filter out the pointless search terms (2 letter name tokens and less) - // they might be right - but they are just too darned expensive to run - if (sizeof($aSearch['aName'])) $aTerms[] = "name_vector @> ARRAY[".join($aSearch['aName'],",")."]"; - //if (sizeof($aSearch['aNameNonSearch'])) $aTerms[] = "array_cat(name_vector,ARRAY[]::integer[]) @> ARRAY[".join($aSearch['aNameNonSearch'],",")."]"; - if (sizeof($aSearch['aAddress']) && $aSearch['aName'] != $aSearch['aAddress']) - { - // For infrequent name terms disable index usage for address - if (CONST_Search_NameOnlySearchFrequencyThreshold && - sizeof($aSearch['aName']) == 1 && - $aWordFrequencyScores[$aSearch['aName'][reset($aSearch['aName'])]] < CONST_Search_NameOnlySearchFrequencyThreshold) - { - //$aTerms[] = "array_cat(nameaddress_vector,ARRAY[]::integer[]) @> ARRAY[".join(array_merge($aSearch['aAddress'],$aSearch['aAddressNonSearch']),",")."]"; - $aTerms[] = "array_cat(nameaddress_vector,ARRAY[]::integer[]) @> ARRAY[".join($aSearch['aAddress'],",")."]"; - } - else - { - $aTerms[] = "nameaddress_vector @> ARRAY[".join($aSearch['aAddress'],",")."]"; - //if (sizeof($aSearch['aAddressNonSearch'])) $aTerms[] = "array_cat(nameaddress_vector,ARRAY[]::integer[]) @> ARRAY[".join($aSearch['aAddressNonSearch'],",")."]"; - } - } - if ($aSearch['sCountryCode']) $aTerms[] = "country_code = '".pg_escape_string($aSearch['sCountryCode'])."'"; - if ($aSearch['sHouseNumber']) - { - $aTerms[] = "address_rank between 16 and 27"; - } - else - { - if ($this->iMinAddressRank > 0) - { - $aTerms[] = "address_rank >= ".$this->iMinAddressRank; - } - if ($this->iMaxAddressRank < 30) - { - $aTerms[] = "address_rank <= ".$this->iMaxAddressRank; - } - } - if ($aSearch['fLon'] && $aSearch['fLat']) - { - $aTerms[] = "ST_DWithin(centroid, ST_SetSRID(ST_Point(".$aSearch['fLon'].",".$aSearch['fLat']."),4326), ".$aSearch['fRadius'].")"; - $aOrder[] = "ST_Distance(centroid, ST_SetSRID(ST_Point(".$aSearch['fLon'].",".$aSearch['fLat']."),4326)) ASC"; - } - if (sizeof($this->aExcludePlaceIDs)) - { - $aTerms[] = "place_id not in (".join(',',$this->aExcludePlaceIDs).")"; - } - if ($sCountryCodesSQL) - { - $aTerms[] = "country_code in ($sCountryCodesSQL)"; - } - - if ($bBoundingBoxSearch) $aTerms[] = "centroid && $this->sViewboxSmallSQL"; - if ($sNearPointSQL) $aOrder[] = "ST_Distance($sNearPointSQL, centroid) asc"; - - if ($aSearch['sHouseNumber']) - { - $sImportanceSQL = '- abs(26 - address_rank) + 3'; - } - else - { - $sImportanceSQL = '(case when importance = 0 OR importance IS NULL then 0.75-(search_rank::float/40) else importance end)'; - } - if ($this->sViewboxSmallSQL) $sImportanceSQL .= " * case when ST_Contains($this->sViewboxSmallSQL, centroid) THEN 1 ELSE 0.5 END"; - if ($this->sViewboxLargeSQL) $sImportanceSQL .= " * case when ST_Contains($this->sViewboxLargeSQL, centroid) THEN 1 ELSE 0.5 END"; - - $aOrder[] = "$sImportanceSQL DESC"; - if (sizeof($aSearch['aFullNameAddress'])) - { - $sExactMatchSQL = '(select count(*) from (select unnest(ARRAY['.join($aSearch['aFullNameAddress'],",").']) INTERSECT select unnest(nameaddress_vector))s) as exactmatch'; - $aOrder[] = 'exactmatch DESC'; - } else { - $sExactMatchSQL = '0::int as exactmatch'; - } - - if (sizeof($aTerms)) - { - $sSQL = "select place_id, "; - $sSQL .= $sExactMatchSQL; - $sSQL .= " from search_name"; - $sSQL .= " where ".join(' and ',$aTerms); - $sSQL .= " order by ".join(', ',$aOrder); - if ($aSearch['sHouseNumber'] || $aSearch['sClass']) - $sSQL .= " limit 20"; - elseif (!sizeof($aSearch['aName']) && !sizeof($aSearch['aAddress']) && $aSearch['sClass']) - $sSQL .= " limit 1"; - else - $sSQL .= " limit ".$this->iLimit; - - if (CONST_Debug) { var_dump($sSQL); } - $aViewBoxPlaceIDs = chksql($this->oDB->getAll($sSQL), - "Could not get places for search terms."); - //var_dump($aViewBoxPlaceIDs); - // Did we have an viewbox matches? - $aPlaceIDs = array(); - $bViewBoxMatch = false; - foreach($aViewBoxPlaceIDs as $aViewBoxRow) - { - //if ($bViewBoxMatch == 1 && $aViewBoxRow['in_small'] == 'f') break; - //if ($bViewBoxMatch == 2 && $aViewBoxRow['in_large'] == 'f') break; - //if ($aViewBoxRow['in_small'] == 't') $bViewBoxMatch = 1; - //else if ($aViewBoxRow['in_large'] == 't') $bViewBoxMatch = 2; - $aPlaceIDs[] = $aViewBoxRow['place_id']; - $this->exactMatchCache[$aViewBoxRow['place_id']] = $aViewBoxRow['exactmatch']; - } - } - //var_Dump($aPlaceIDs); - //exit; - - //now search for housenumber, if housenumber provided - if ($aSearch['sHouseNumber'] && sizeof($aPlaceIDs)) - { - $searchedHousenumber = intval($aSearch['sHouseNumber']); - $aRoadPlaceIDs = $aPlaceIDs; - $sPlaceIDs = join(',',$aPlaceIDs); - - // Now they are indexed, look for a house attached to a street we found - $sHouseNumberRegex = '\\\\m'.$aSearch['sHouseNumber'].'\\\\M'; - $sSQL = "select place_id from placex where parent_place_id in (".$sPlaceIDs.") and transliteration(housenumber) ~* E'".$sHouseNumberRegex."'"; - if (sizeof($this->aExcludePlaceIDs)) - { - $sSQL .= " and place_id not in (".join(',',$this->aExcludePlaceIDs).")"; - } - $sSQL .= " limit $this->iLimit"; - if (CONST_Debug) var_dump($sSQL); - $aPlaceIDs = chksql($this->oDB->getCol($sSQL)); - - // if nothing found, search in the interpolation line table - if(!sizeof($aPlaceIDs)) - { - // do we need to use transliteration and the regex for housenumbers??? - //new query for lines, not housenumbers anymore - if($searchedHousenumber%2 == 0){ - //if housenumber is even, look for housenumber in streets with interpolationtype even or all - $sSQL = "select distinct place_id from location_property_osmline where parent_place_id in (".$sPlaceIDs.") and (interpolationtype='even' or interpolationtype='all') and ".$searchedHousenumber.">=startnumber and ".$searchedHousenumber."<=endnumber"; - }else{ - //look for housenumber in streets with interpolationtype odd or all - $sSQL = "select distinct place_id from location_property_osmline where parent_place_id in (".$sPlaceIDs.") and (interpolationtype='odd' or interpolationtype='all') and ".$searchedHousenumber.">=startnumber and ".$searchedHousenumber."<=endnumber"; - } - - if (sizeof($this->aExcludePlaceIDs)) - { - $sSQL .= " and place_id not in (".join(',', $this->aExcludePlaceIDs).")"; - } - //$sSQL .= " limit $this->iLimit"; - if (CONST_Debug) var_dump($sSQL); - //get place IDs - $aPlaceIDs = chksql($this->oDB->getCol($sSQL, 0)); - } - - // If nothing found try the aux fallback table - if (CONST_Use_Aux_Location_data && !sizeof($aPlaceIDs)) - { - $sSQL = "select place_id from location_property_aux where parent_place_id in (".$sPlaceIDs.") and housenumber = '".pg_escape_string($aSearch['sHouseNumber'])."'"; - if (sizeof($this->aExcludePlaceIDs)) - { - $sSQL .= " and parent_place_id not in (".join(',',$this->aExcludePlaceIDs).")"; - } - //$sSQL .= " limit $this->iLimit"; - if (CONST_Debug) var_dump($sSQL); - $aPlaceIDs = chksql($this->oDB->getCol($sSQL)); - } - - //if nothing was found in placex or location_property_aux, then search in Tiger data for this housenumber(location_property_tiger) - if (CONST_Use_US_Tiger_Data && !sizeof($aPlaceIDs)) - { - //new query for lines, not housenumbers anymore - if($searchedHousenumber%2 == 0){ - //if housenumber is even, look for housenumber in streets with interpolationtype even or all - $sSQL = "select distinct place_id from location_property_tiger where parent_place_id in (".$sPlaceIDs.") and (interpolationtype='even' or interpolationtype='all') and ".$searchedHousenumber.">=startnumber and ".$searchedHousenumber."<=endnumber"; - }else{ - //look for housenumber in streets with interpolationtype odd or all - $sSQL = "select distinct place_id from location_property_tiger where parent_place_id in (".$sPlaceIDs.") and (interpolationtype='odd' or interpolationtype='all') and ".$searchedHousenumber.">=startnumber and ".$searchedHousenumber."<=endnumber"; - } - - if (sizeof($this->aExcludePlaceIDs)) - { - $sSQL .= " and place_id not in (".join(',', $this->aExcludePlaceIDs).")"; - } - //$sSQL .= " limit $this->iLimit"; - if (CONST_Debug) var_dump($sSQL); - //get place IDs - $aPlaceIDs = chksql($this->oDB->getCol($sSQL, 0)); - } - - // Fallback to the road (if no housenumber was found) - if (!sizeof($aPlaceIDs) && preg_match('/[0-9]+/', $aSearch['sHouseNumber'])) - { - $aPlaceIDs = $aRoadPlaceIDs; - //set to -1, if no housenumbers were found - $searchedHousenumber = -1; - } - //else: housenumber was found, remains saved in searchedHousenumber - } - - - if ($aSearch['sClass'] && sizeof($aPlaceIDs)) - { - $sPlaceIDs = join(',', $aPlaceIDs); - $aClassPlaceIDs = array(); - - if (!$aSearch['sOperator'] || $aSearch['sOperator'] == 'name') - { - // 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 calculated_country_code in ($sCountryCodesSQL)"; - $sSQL .= " order by rank_search asc limit $this->iLimit"; - if (CONST_Debug) var_dump($sSQL); - $aClassPlaceIDs = chksql($this->oDB->getCol($sSQL)); - } - - if (!$aSearch['sOperator'] || $aSearch['sOperator'] == 'near') // & in - { - $sSQL = "select count(*) from pg_tables where tablename = 'place_classtype_".$aSearch['sClass']."_".$aSearch['sType']."'"; - $bCacheTable = chksql($this->oDB->getOne($sSQL)); - - $sSQL = "select min(rank_search) from placex where place_id in ($sPlaceIDs)"; - - if (CONST_Debug) var_dump($sSQL); - $this->iMaxRank = ((int)chksql($this->oDB->getOne($sSQL))); - - // For state / country level searches the normal radius search doesn't work very well - $sPlaceGeom = false; - if ($this->iMaxRank < 9 && $bCacheTable) - { - // Try and get a polygon to search in instead - $sSQL = "select geometry from placex where place_id in ($sPlaceIDs) and rank_search < $this->iMaxRank + 5 and st_geometrytype(geometry) in ('ST_Polygon','ST_MultiPolygon') order by rank_search asc limit 1"; - if (CONST_Debug) var_dump($sSQL); - $sPlaceGeom = chksql($this->oDB->getOne($sSQL)); - } - - if ($sPlaceGeom) - { - $sPlaceIDs = false; - } - else - { - $this->iMaxRank += 5; - $sSQL = "select place_id from placex where place_id in ($sPlaceIDs) and rank_search < $this->iMaxRank"; - if (CONST_Debug) var_dump($sSQL); - $aPlaceIDs = chksql($this->oDB->getCol($sSQL)); - $sPlaceIDs = join(',',$aPlaceIDs); - } - - if ($sPlaceIDs || $sPlaceGeom) - { - - $fRange = 0.01; - if ($bCacheTable) - { - // More efficient - can make the range bigger - $fRange = 0.05; - - $sOrderBySQL = ''; - if ($sNearPointSQL) $sOrderBySQL = "ST_Distance($sNearPointSQL, l.centroid)"; - else if ($sPlaceIDs) $sOrderBySQL = "ST_Distance(l.centroid, f.geometry)"; - else if ($sPlaceGeom) $sOrderBysSQL = "ST_Distance(st_centroid('".$sPlaceGeom."'), l.centroid)"; - - $sSQL = "select distinct l.place_id".($sOrderBySQL?','.$sOrderBySQL:'')." from place_classtype_".$aSearch['sClass']."_".$aSearch['sType']." as l"; - if ($sCountryCodesSQL) $sSQL .= " join placex as lp using (place_id)"; - if ($sPlaceIDs) - { - $sSQL .= ",placex as f where "; - $sSQL .= "f.place_id in ($sPlaceIDs) and ST_DWithin(l.centroid, f.centroid, $fRange) "; - } - if ($sPlaceGeom) - { - $sSQL .= " where "; - $sSQL .= "ST_Contains('".$sPlaceGeom."', l.centroid) "; - } - if (sizeof($this->aExcludePlaceIDs)) - { - $sSQL .= " and l.place_id not in (".join(',',$this->aExcludePlaceIDs).")"; - } - if ($sCountryCodesSQL) $sSQL .= " and lp.calculated_country_code in ($sCountryCodesSQL)"; - if ($sOrderBySQL) $sSQL .= "order by ".$sOrderBySQL." asc"; - if ($this->iOffset) $sSQL .= " offset $this->iOffset"; - $sSQL .= " limit $this->iLimit"; - if (CONST_Debug) var_dump($sSQL); - $aClassPlaceIDs = array_merge($aClassPlaceIDs, chksql($this->oDB->getCol($sSQL))); - } - else - { - if (isset($aSearch['fRadius']) && $aSearch['fRadius']) $fRange = $aSearch['fRadius']; - - $sOrderBySQL = ''; - if ($sNearPointSQL) $sOrderBySQL = "ST_Distance($sNearPointSQL, l.geometry)"; - else $sOrderBySQL = "ST_Distance(l.geometry, f.geometry)"; - - $sSQL = "select distinct l.place_id".($sOrderBysSQL?','.$sOrderBysSQL:'')." from placex as l,placex as f where "; - $sSQL .= "f.place_id in ( $sPlaceIDs) and ST_DWithin(l.geometry, f.centroid, $fRange) "; - $sSQL .= "and l.class='".$aSearch['sClass']."' and l.type='".$aSearch['sType']."' "; - if (sizeof($this->aExcludePlaceIDs)) - { - $sSQL .= " and l.place_id not in (".join(',',$this->aExcludePlaceIDs).")"; - } - if ($sCountryCodesSQL) $sSQL .= " and l.calculated_country_code in ($sCountryCodesSQL)"; - if ($sOrderBy) $sSQL .= "order by ".$OrderBysSQL." asc"; - if ($this->iOffset) $sSQL .= " offset $this->iOffset"; - $sSQL .= " limit $this->iLimit"; - if (CONST_Debug) var_dump($sSQL); - $aClassPlaceIDs = array_merge($aClassPlaceIDs, chksql($this->oDB->getCol($sSQL))); - } - } - } - - $aPlaceIDs = $aClassPlaceIDs; - - } - - } - - if (CONST_Debug) { echo "
Place IDs: "; var_Dump($aPlaceIDs); } - - foreach($aPlaceIDs as $iPlaceID) - { - // array for placeID => -1 | Tiger housenumber - $aResultPlaceIDs[$iPlaceID] = $searchedHousenumber; - } - if ($iQueryLoop > 20) break; - } - - if (isset($aResultPlaceIDs) && sizeof($aResultPlaceIDs) && ($this->iMinAddressRank != 0 || $this->iMaxAddressRank != 30)) - { - // Need to verify passes rank limits before dropping out of the loop (yuk!) - // reduces the number of place ids, like a filter - // rank_address is 30 for interpolated housenumbers - $sSQL = "select place_id from placex where place_id in (".join(',',array_keys($aResultPlaceIDs)).") "; - $sSQL .= "and (placex.rank_address between $this->iMinAddressRank and $this->iMaxAddressRank "; - if (14 >= $this->iMinAddressRank && 14 <= $this->iMaxAddressRank) $sSQL .= " OR (extratags->'place') = 'city'"; - if ($this->aAddressRankList) $sSQL .= " OR placex.rank_address in (".join(',',$this->aAddressRankList).")"; - if (CONST_Use_US_Tiger_Data) - { - $sSQL .= ") UNION select place_id from location_property_tiger where place_id in (".join(',',array_keys($aResultPlaceIDs)).") "; - $sSQL .= "and (30 between $this->iMinAddressRank and $this->iMaxAddressRank "; - if ($this->aAddressRankList) $sSQL .= " OR 30 in (".join(',',$this->aAddressRankList).")"; - } - $sSQL .= ") UNION select place_id from location_property_osmline where place_id in (".join(',',array_keys($aResultPlaceIDs)).")"; - $sSQL .= " and (30 between $this->iMinAddressRank and $this->iMaxAddressRank)"; - if (CONST_Debug) var_dump($sSQL); - $aFilteredPlaceIDs = chksql($this->oDB->getCol($sSQL)); - $tempIDs = array(); - foreach($aFilteredPlaceIDs as $placeID) - { - $tempIDs[$placeID] = $aResultPlaceIDs[$placeID]; //assign housenumber to placeID - } - $aResultPlaceIDs = $tempIDs; - } - - //exit; - if (isset($aResultPlaceIDs) && sizeof($aResultPlaceIDs)) break; - if ($iGroupLoop > 4) break; - if ($iQueryLoop > 30) break; - } - - // Did we find anything? - if (isset($aResultPlaceIDs) && sizeof($aResultPlaceIDs)) - { - $aSearchResults = $this->getDetails($aResultPlaceIDs); - } - - } - else - { - // Just interpret as a reverse geocode - $oReverse = new ReverseGeocode($this->oDB); - $oReverse->setZoom(18); - - $aLookup = $oReverse->lookup((float)$this->aNearPoint[0], - (float)$this->aNearPoint[1], - false); - - if (CONST_Debug) var_dump("Reverse search", $aLookup); - - if ($aLookup['place_id']) - $aSearchResults = $this->getDetails(array($aLookup['place_id'] => -1)); - else - $aSearchResults = array(); - } - - // No results? Done - if (!sizeof($aSearchResults)) - { - if ($this->bFallback) - { - if ($this->fallbackStructuredQuery()) - { - return $this->lookup(); - } - } - - return array(); - } - - $aClassType = getClassTypesWithImportance(); - $aRecheckWords = preg_split('/\b[\s,\\-]*/u',$sQuery); - foreach($aRecheckWords as $i => $sWord) - { - if (!preg_match('/\pL/', $sWord)) unset($aRecheckWords[$i]); - } - - if (CONST_Debug) { echo 'Recheck words:<\i>'; var_dump($aRecheckWords); } - - $oPlaceLookup = new PlaceLookup($this->oDB); - $oPlaceLookup->setIncludePolygonAsPoints($this->bIncludePolygonAsPoints); - $oPlaceLookup->setIncludePolygonAsText($this->bIncludePolygonAsText); - $oPlaceLookup->setIncludePolygonAsGeoJSON($this->bIncludePolygonAsGeoJSON); - $oPlaceLookup->setIncludePolygonAsKML($this->bIncludePolygonAsKML); - $oPlaceLookup->setIncludePolygonAsSVG($this->bIncludePolygonAsSVG); - $oPlaceLookup->setPolygonSimplificationThreshold($this->fPolygonSimplificationThreshold); - - foreach($aSearchResults as $iResNum => $aResult) - { - // Default - $fDiameter = getResultDiameter($aResult); - - $aOutlineResult = $oPlaceLookup->getOutlines($aResult['place_id'], $aResult['lon'], $aResult['lat'], $fDiameter/2); - if ($aOutlineResult) - { - $aResult = array_merge($aResult, $aOutlineResult); - } - - if ($aResult['extra_place'] == 'city') - { - $aResult['class'] = 'place'; - $aResult['type'] = 'city'; - $aResult['rank_search'] = 16; - } - - // Is there an icon set for this type of result? - if (isset($aClassType[$aResult['class'].':'.$aResult['type']]['icon']) - && $aClassType[$aResult['class'].':'.$aResult['type']]['icon']) - { - $aResult['icon'] = CONST_Website_BaseURL.'images/mapicons/'.$aClassType[$aResult['class'].':'.$aResult['type']]['icon'].'.p.20.png'; - } - - if (isset($aClassType[$aResult['class'].':'.$aResult['type'].':'.$aResult['admin_level']]['label']) - && $aClassType[$aResult['class'].':'.$aResult['type'].':'.$aResult['admin_level']]['label']) - { - $aResult['label'] = $aClassType[$aResult['class'].':'.$aResult['type'].':'.$aResult['admin_level']]['label']; - } - elseif (isset($aClassType[$aResult['class'].':'.$aResult['type']]['label']) - && $aClassType[$aResult['class'].':'.$aResult['type']]['label']) - { - $aResult['label'] = $aClassType[$aResult['class'].':'.$aResult['type']]['label']; - } - // if tag '&addressdetails=1' is set in query - if ($this->bIncludeAddressDetails) - { - // getAddressDetails() is defined in lib.php and uses the SQL function get_addressdata in functions.sql - $aResult['address'] = getAddressDetails($this->oDB, $sLanguagePrefArraySQL, $aResult['place_id'], $aResult['country_code'], $aResultPlaceIDs[$aResult['place_id']]); - if ($aResult['extra_place'] == 'city' && !isset($aResult['address']['city'])) - { - $aResult['address'] = array_merge(array('city' => array_shift(array_values($aResult['address']))), $aResult['address']); - } - } - if ($this->bIncludeExtraTags) - { - if ($aResult['extra']) - { - $aResult['sExtraTags'] = json_decode($aResult['extra']); - } - else - { - $aResult['sExtraTags'] = (object) array(); - } - } - - if ($this->bIncludeNameDetails) - { - if ($aResult['names']) - { - $aResult['sNameDetails'] = json_decode($aResult['names']); - } - else - { - $aResult['sNameDetails'] = (object) array(); - } - } - - // Adjust importance for the number of exact string matches in the result - $aResult['importance'] = max(0.001,$aResult['importance']); - $iCountWords = 0; - $sAddress = $aResult['langaddress']; - foreach($aRecheckWords as $i => $sWord) - { - if (stripos($sAddress, $sWord)!==false) - { - $iCountWords++; - if (preg_match("/(^|,)\s*".preg_quote($sWord, '/')."\s*(,|$)/", $sAddress)) $iCountWords += 0.1; - } - } - - $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 - - $aResult['name'] = $aResult['langaddress']; - // secondary ordering (for results with same importance (the smaller the better): - // - approximate importance of address parts - $aResult['foundorder'] = -$aResult['addressimportance']/10; - // - number of exact matches from the query - if (isset($this->exactMatchCache[$aResult['place_id']])) - $aResult['foundorder'] -= $this->exactMatchCache[$aResult['place_id']]; - else if (isset($this->exactMatchCache[$aResult['parent_place_id']])) - $aResult['foundorder'] -= $this->exactMatchCache[$aResult['parent_place_id']]; - // - importance of the class/type - if (isset($aClassType[$aResult['class'].':'.$aResult['type']]['importance']) - && $aClassType[$aResult['class'].':'.$aResult['type']]['importance']) - { - $aResult['foundorder'] += 0.0001 * $aClassType[$aResult['class'].':'.$aResult['type']]['importance']; - } - else - { - $aResult['foundorder'] += 0.01; - } - if (CONST_Debug) { var_dump($aResult); } - $aSearchResults[$iResNum] = $aResult; - } - uasort($aSearchResults, 'byImportance'); - - $aOSMIDDone = array(); - $aClassTypeNameDone = array(); - $aToFilter = $aSearchResults; - $aSearchResults = array(); - - $bFirst = true; - foreach($aToFilter as $iResNum => $aResult) - { - $this->aExcludePlaceIDs[$aResult['place_id']] = $aResult['place_id']; - if ($bFirst) - { - $fLat = $aResult['lat']; - $fLon = $aResult['lon']; - if (isset($aResult['zoom'])) $iZoom = $aResult['zoom']; - $bFirst = false; - } - if (!$this->bDeDupe || (!isset($aOSMIDDone[$aResult['osm_type'].$aResult['osm_id']]) - && !isset($aClassTypeNameDone[$aResult['osm_type'].$aResult['class'].$aResult['type'].$aResult['name'].$aResult['admin_level']]))) - { - $aOSMIDDone[$aResult['osm_type'].$aResult['osm_id']] = true; - $aClassTypeNameDone[$aResult['osm_type'].$aResult['class'].$aResult['type'].$aResult['name'].$aResult['admin_level']] = true; - $aSearchResults[] = $aResult; - } - - // Absolute limit on number of results - if (sizeof($aSearchResults) >= $this->iFinalLimit) break; - } - - return $aSearchResults; - - } // end lookup() - - - } // end class + namespace Nominatim; + + require_once(CONST_BasePath.'/lib/PlaceLookup.php'); + require_once(CONST_BasePath.'/lib/ReverseGeocode.php'); + + class Geocode + { + protected $oDB; + + protected $aLangPrefOrder = array(); + + protected $bIncludeAddressDetails = false; + protected $bIncludeExtraTags = false; + protected $bIncludeNameDetails = false; + + protected $bIncludePolygonAsPoints = false; + protected $bIncludePolygonAsText = false; + protected $bIncludePolygonAsGeoJSON = false; + protected $bIncludePolygonAsKML = false; + protected $bIncludePolygonAsSVG = false; + protected $fPolygonSimplificationThreshold = 0.0; + + protected $aExcludePlaceIDs = array(); + protected $bDeDupe = true; - protected $bReverseInPlan = false; ++ protected $bReverseInPlan = true; + + protected $iLimit = 20; + protected $iFinalLimit = 10; + protected $iOffset = 0; + protected $bFallback = false; + + protected $aCountryCodes = false; + protected $aNearPoint = false; + + protected $bBoundedSearch = false; + protected $aViewBox = false; + protected $sViewboxCentreSQL = false; + protected $sViewboxSmallSQL = false; + protected $sViewboxLargeSQL = false; + + protected $iMaxRank = 20; + protected $iMinAddressRank = 0; + protected $iMaxAddressRank = 30; + protected $aAddressRankList = array(); + protected $exactMatchCache = array(); + + protected $sAllowedTypesSQLList = false; + + protected $sQuery = false; + protected $aStructuredQuery = false; + + + public function __construct(&$oDB) + { + $this->oDB =& $oDB; + } + + public function setReverseInPlan($bReverse) + { + $this->bReverseInPlan = $bReverse; + } + + public function setLanguagePreference($aLangPref) + { + $this->aLangPrefOrder = $aLangPref; + } + + public function getIncludeAddressDetails() + { + return $this->bIncludeAddressDetails; + } + + public function getIncludeExtraTags() + { + return $this->bIncludeExtraTags; + } + + public function getIncludeNameDetails() + { + return $this->bIncludeNameDetails; + } + + public function setIncludePolygonAsPoints($b = true) + { + $this->bIncludePolygonAsPoints = $b; + } + + public function setIncludePolygonAsText($b = true) + { + $this->bIncludePolygonAsText = $b; + } + + public function setIncludePolygonAsGeoJSON($b = true) + { + $this->bIncludePolygonAsGeoJSON = $b; + } + + public function setIncludePolygonAsKML($b = true) + { + $this->bIncludePolygonAsKML = $b; + } + + public function setIncludePolygonAsSVG($b = true) + { + $this->bIncludePolygonAsSVG = $b; + } + + public function setPolygonSimplificationThreshold($f) + { + $this->fPolygonSimplificationThreshold = $f; + } + + public function setLimit($iLimit = 10) + { + if ($iLimit > 50) $iLimit = 50; + if ($iLimit < 1) $iLimit = 1; + + $this->iFinalLimit = $iLimit; + $this->iLimit = $iLimit + min($iLimit, 10); + } + + public function getExcludedPlaceIDs() + { + return $this->aExcludePlaceIDs; + } + + public function getViewBoxString() + { + if (!$this->aViewBox) return null; + return $this->aViewBox[0].','.$this->aViewBox[3].','.$this->aViewBox[2].','.$this->aViewBox[1]; + } + + public function setFeatureType($sFeatureType) + { + switch ($sFeatureType) { + case 'country': + $this->setRankRange(4, 4); + break; + case 'state': + $this->setRankRange(8, 8); + break; + case 'city': + $this->setRankRange(14, 16); + break; + case 'settlement': + $this->setRankRange(8, 20); + break; + } + } + + public function setRankRange($iMin, $iMax) + { + $this->iMinAddressRank = $iMin; + $this->iMaxAddressRank = $iMax; + } + + public function setRoute($aRoutePoints, $fRouteWidth) + { + $this->aViewBox = false; + + $this->sViewboxCentreSQL = "ST_SetSRID('LINESTRING("; + $sSep = ''; + foreach ($this->aRoutePoints as $aPoint) { + $fPoint = (float)$aPoint; + $this->sViewboxCentreSQL .= $sSep.$fPoint; + $sSep = ($sSep == ' ') ? ',' : ' '; + } + $this->sViewboxCentreSQL .= ")'::geometry,4326)"; + + $this->sViewboxSmallSQL = 'st_buffer('.$this->sViewboxCentreSQL; + $this->sViewboxSmallSQL .= ','.($fRouteWidth/69).')'; + + $this->sViewboxLargeSQL = 'st_buffer('.$this->sViewboxCentreSQL; + $this->sViewboxLargeSQL .= ','.($fRouteWidth/30).')'; + } + + public function setViewbox($aViewbox) + { + $this->aViewBox = array_map('floatval', $aViewbox); + + $fHeight = $this->aViewBox[0] - $this->aViewBox[2]; + $fWidth = $this->aViewBox[1] - $this->aViewBox[3]; + $aBigViewBox[0] = $this->aViewBox[0] + $fHeight; + $aBigViewBox[2] = $this->aViewBox[2] - $fHeight; + $aBigViewBox[1] = $this->aViewBox[1] + $fWidth; + $aBigViewBox[3] = $this->aViewBox[3] - $fWidth; + + $this->sViewboxCentreSQL = false; + $this->sViewboxSmallSQL = "ST_SetSRID(ST_MakeBox2D(ST_Point(".$this->aViewBox[0].",".$this->aViewBox[1]."),ST_Point(".$this->aViewBox[2].",".$this->aViewBox[3].")),4326)"; + $this->sViewboxLargeSQL = "ST_SetSRID(ST_MakeBox2D(ST_Point(".$aBigViewBox[0].",".$aBigViewBox[1]."),ST_Point(".$aBigViewBox[2].",".$aBigViewBox[3].")),4326)"; + } + + public function setNearPoint($aNearPoint, $fRadiusDeg = 0.1) + { + $this->aNearPoint = array((float)$aNearPoint[0], (float)$aNearPoint[1], (float)$fRadiusDeg); + } + + public function setQuery($sQueryString) + { + $this->sQuery = $sQueryString; + $this->aStructuredQuery = false; + } + + public function getQueryString() + { + return $this->sQuery; + } + + + public function loadParamArray($oParams) + { + $this->bIncludeAddressDetails + = $oParams->getBool('addressdetails', $this->bIncludeAddressDetails); + $this->bIncludeExtraTags + = $oParams->getBool('extratags', $this->bIncludeExtraTags); + $this->bIncludeNameDetails + = $oParams->getBool('namedetails', $this->bIncludeNameDetails); + + $this->bBoundedSearch = $oParams->getBool('bounded', $this->bBoundedSearch); + $this->bDeDupe = $oParams->getBool('dedupe', $this->bDeDupe); + + $this->setLimit($oParams->getInt('limit', $this->iFinalLimit)); + $this->iOffset = $oParams->getInt('offset', $this->iOffset); + + $this->bFallback = $oParams->getBool('fallback', $this->bFallback); + + // List of excluded Place IDs - used for more acurate pageing + $sExcluded = $oParams->getStringList('exclude_place_ids'); + if ($sExcluded) { + foreach ($sExcluded as $iExcludedPlaceID) { + $iExcludedPlaceID = (int)$iExcludedPlaceID; + if ($iExcludedPlaceID) + $aExcludePlaceIDs[$iExcludedPlaceID] = $iExcludedPlaceID; + } + + if (isset($aExcludePlaceIDs)) + $this->aExcludePlaceIDs = $aExcludePlaceIDs; + } + + // Only certain ranks of feature + $sFeatureType = $oParams->getString('featureType'); + if (!$sFeatureType) $sFeatureType = $oParams->getString('featuretype'); + if ($sFeatureType) $this->setFeatureType($sFeatureType); + + // Country code list + $sCountries = $oParams->getStringList('countrycodes'); + if ($sCountries) { + foreach ($sCountries as $sCountryCode) { + if (preg_match('/^[a-zA-Z][a-zA-Z]$/', $sCountryCode)) { + $aCountries[] = strtolower($sCountryCode); + } + } + if (isset($aCountryCodes)) + $this->aCountryCodes = $aCountries; + } + + $aViewbox = $oParams->getStringList('viewboxlbrt'); + if ($aViewbox) { + $this->setViewbox($aViewbox); + } else { + $aViewbox = $oParams->getStringList('viewbox'); + if ($aViewbox) { + $this->setViewBox(array( + $aViewbox[0], + $aViewbox[3], + $aViewbox[2], + $aViewbox[1] + )); + } else { + $aRoute = $oParams->getStringList('route'); + $fRouteWidth = $oParams->getFloat('routewidth'); + if ($aRoute && $fRouteWidth) { + $this->setRoute($aRoute, $fRouteWidth); + } + } + } + } + + public function setQueryFromParams($oParams) + { + // Search query + $sQuery = $oParams->getString('q'); + if (!$sQuery) { + $this->setStructuredQuery( + $oParams->getString('amenity'), + $oParams->getString('street'), + $oParams->getString('city'), + $oParams->getString('county'), + $oParams->getString('state'), + $oParams->getString('country'), + $oParams->getString('postalcode') + ); + $this->setReverseInPlan(false); + } else { + $this->setQuery($sQuery); + } + } + + public function loadStructuredAddressElement($sValue, $sKey, $iNewMinAddressRank, $iNewMaxAddressRank, $aItemListValues) + { + $sValue = trim($sValue); + if (!$sValue) return false; + $this->aStructuredQuery[$sKey] = $sValue; + if ($this->iMinAddressRank == 0 && $this->iMaxAddressRank == 30) { + $this->iMinAddressRank = $iNewMinAddressRank; + $this->iMaxAddressRank = $iNewMaxAddressRank; + } + if ($aItemListValues) $this->aAddressRankList = array_merge($this->aAddressRankList, $aItemListValues); + return true; + } + + public function setStructuredQuery($sAmentiy = false, $sStreet = false, $sCity = false, $sCounty = false, $sState = false, $sCountry = false, $sPostalCode = false) + { + $this->sQuery = false; + + // Reset + $this->iMinAddressRank = 0; + $this->iMaxAddressRank = 30; + $this->aAddressRankList = array(); + + $this->aStructuredQuery = array(); + $this->sAllowedTypesSQLList = ''; + + $this->loadStructuredAddressElement($sAmentiy, 'amenity', 26, 30, false); + $this->loadStructuredAddressElement($sStreet, 'street', 26, 30, false); + $this->loadStructuredAddressElement($sCity, 'city', 14, 24, false); + $this->loadStructuredAddressElement($sCounty, 'county', 9, 13, false); + $this->loadStructuredAddressElement($sState, 'state', 8, 8, false); + $this->loadStructuredAddressElement($sPostalCode, 'postalcode', 5, 11, array(5, 11)); + $this->loadStructuredAddressElement($sCountry, 'country', 4, 4, false); + + if (sizeof($this->aStructuredQuery) > 0) { + $this->sQuery = join(', ', $this->aStructuredQuery); + if ($this->iMaxAddressRank < 30) { + $sAllowedTypesSQLList = '(\'place\',\'boundary\')'; + } + } + } + + public function fallbackStructuredQuery() + { + if (!$this->aStructuredQuery) return false; + + $aParams = $this->aStructuredQuery; + + if (sizeof($aParams) == 1) return false; + + $aOrderToFallback = array('postalcode', 'street', 'city', 'county', 'state'); + + foreach ($aOrderToFallback as $sType) { + if (isset($aParams[$sType])) { + unset($aParams[$sType]); + $this->setStructuredQuery(@$aParams['amenity'], @$aParams['street'], @$aParams['city'], @$aParams['county'], @$aParams['state'], @$aParams['country'], @$aParams['postalcode']); + return true; + } + } + + return false; + } + + public function getDetails($aPlaceIDs) + { + //$aPlaceIDs is an array with key: placeID and value: tiger-housenumber, if found, else -1 + if (sizeof($aPlaceIDs) == 0) return array(); + + $sLanguagePrefArraySQL = "ARRAY[".join(',', array_map("getDBQuoted", $this->aLangPrefOrder))."]"; + + // Get the details for display (is this a redundant extra step?) + $sPlaceIDs = join(',', array_keys($aPlaceIDs)); + + $sImportanceSQL = ''; + if ($this->sViewboxSmallSQL) $sImportanceSQL .= " case when ST_Contains($this->sViewboxSmallSQL, ST_Collect(centroid)) THEN 1 ELSE 0.75 END * "; + if ($this->sViewboxLargeSQL) $sImportanceSQL .= " case when ST_Contains($this->sViewboxLargeSQL, ST_Collect(centroid)) THEN 1 ELSE 0.75 END * "; + + $sSQL = "select osm_type,osm_id,class,type,admin_level,rank_search,rank_address,min(place_id) as place_id, min(parent_place_id) as parent_place_id, calculated_country_code as country_code,"; + $sSQL .= "get_address_by_language(place_id, -1, $sLanguagePrefArraySQL) as langaddress,"; + $sSQL .= "get_name_by_language(name, $sLanguagePrefArraySQL) as placename,"; + $sSQL .= "get_name_by_language(name, ARRAY['ref']) as ref,"; + if ($this->bIncludeExtraTags) $sSQL .= "hstore_to_json(extratags)::text as extra,"; + if ($this->bIncludeNameDetails) $sSQL .= "hstore_to_json(name)::text as names,"; + $sSQL .= "avg(ST_X(centroid)) as lon,avg(ST_Y(centroid)) as lat, "; + $sSQL .= $sImportanceSQL."coalesce(importance,0.75-(rank_search::float/40)) as importance, "; + $sSQL .= "(select max(p.importance*(p.rank_address+2)) from place_addressline s, placex p where s.place_id = min(CASE WHEN placex.rank_search < 28 THEN placex.place_id ELSE placex.parent_place_id END) and p.place_id = s.address_place_id and s.isaddress and p.importance is not null) as addressimportance, "; + $sSQL .= "(extratags->'place') as extra_place "; + $sSQL .= "from placex where place_id in ($sPlaceIDs) "; + $sSQL .= "and (placex.rank_address between $this->iMinAddressRank and $this->iMaxAddressRank "; + if (14 >= $this->iMinAddressRank && 14 <= $this->iMaxAddressRank) $sSQL .= " OR (extratags->'place') = 'city'"; + if ($this->aAddressRankList) $sSQL .= " OR placex.rank_address in (".join(',', $this->aAddressRankList).")"; + $sSQL .= ") "; + if ($this->sAllowedTypesSQLList) $sSQL .= "and placex.class in $this->sAllowedTypesSQLList "; + $sSQL .= "and linked_place_id is null "; + $sSQL .= "group by osm_type,osm_id,class,type,admin_level,rank_search,rank_address,calculated_country_code,importance"; + if (!$this->bDeDupe) $sSQL .= ",place_id"; + $sSQL .= ",langaddress "; + $sSQL .= ",placename "; + $sSQL .= ",ref "; + if ($this->bIncludeExtraTags) $sSQL .= ",extratags"; + if ($this->bIncludeNameDetails) $sSQL .= ",name"; + $sSQL .= ",extratags->'place' "; + + if (30 >= $this->iMinAddressRank && 30 <= $this->iMaxAddressRank) { + // only Tiger housenumbers and interpolation lines need to be interpolated, because they are saved as lines + // with start- and endnumber, the common osm housenumbers are usually saved as points + $sHousenumbers = ""; + $i = 0; + $length = count($aPlaceIDs); + foreach ($aPlaceIDs as $placeID => $housenumber) { + $i++; + $sHousenumbers .= "(".$placeID.", ".$housenumber.")"; + if ($i<$length) $sHousenumbers .= ", "; + } + if (CONST_Use_US_Tiger_Data) { + // Tiger search only if a housenumber was searched and if it was found (i.e. aPlaceIDs[placeID] = housenumber != -1) (realized through a join) + $sSQL .= " union"; + $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, min(parent_place_id) as parent_place_id, 'us' as country_code"; + $sSQL .= ", get_address_by_language(place_id, housenumber_for_place, $sLanguagePrefArraySQL) as langaddress "; + $sSQL .= ", null as placename"; + $sSQL .= ", null as ref"; + if ($this->bIncludeExtraTags) $sSQL .= ", null as extra"; + if ($this->bIncludeNameDetails) $sSQL .= ", null as names"; + $sSQL .= ", avg(st_x(centroid)) as lon, avg(st_y(centroid)) as lat,"; + $sSQL .= $sImportanceSQL."-1.15 as importance "; + $sSQL .= ", (select max(p.importance*(p.rank_address+2)) from place_addressline s, placex p where s.place_id = min(blub.parent_place_id) and p.place_id = s.address_place_id and s.isaddress and p.importance is not null) as addressimportance "; + $sSQL .= ", null as extra_place "; + $sSQL .= " from (select place_id"; + // interpolate the Tiger housenumbers here + $sSQL .= ", ST_LineInterpolatePoint(linegeo, (housenumber_for_place-startnumber::float)/(endnumber-startnumber)::float) as centroid, parent_place_id, housenumber_for_place"; + $sSQL .= " from (location_property_tiger "; + $sSQL .= " join (values ".$sHousenumbers.") as housenumbers(place_id, housenumber_for_place) using(place_id)) "; + $sSQL .= " where housenumber_for_place>=0 and 30 between $this->iMinAddressRank and $this->iMaxAddressRank) as blub"; //postgres wants an alias here + $sSQL .= " group by place_id, housenumber_for_place"; //is this group by really needed?, place_id + housenumber (in combination) are unique + if (!$this->bDeDupe) $sSQL .= ", place_id "; + } + // osmline + // interpolation line search only if a housenumber was searched and if it was found (i.e. aPlaceIDs[placeID] = housenumber != -1) (realized through a join) + $sSQL .= " union "; + $sSQL .= "select 'W' 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, min(parent_place_id) as parent_place_id, calculated_country_code as country_code, "; + $sSQL .= "get_address_by_language(place_id, housenumber_for_place, $sLanguagePrefArraySQL) as langaddress, "; + $sSQL .= "null as placename, "; + $sSQL .= "null as ref, "; + if ($this->bIncludeExtraTags) $sSQL .= "null as extra, "; + if ($this->bIncludeNameDetails) $sSQL .= "null as names, "; + $sSQL .= " avg(st_x(centroid)) as lon, avg(st_y(centroid)) as lat,"; + $sSQL .= $sImportanceSQL."-0.1 as importance, "; // slightly smaller than the importance for normal houses with rank 30, which is 0 + $sSQL .= " (select max(p.importance*(p.rank_address+2)) from place_addressline s, placex p"; + $sSQL .= " where s.place_id = min(blub.parent_place_id) and p.place_id = s.address_place_id and s.isaddress and p.importance is not null) as addressimportance,"; + $sSQL .= " null as extra_place "; + $sSQL .= " from (select place_id, calculated_country_code "; + // interpolate the housenumbers here + $sSQL .= ", CASE WHEN startnumber != endnumber THEN ST_LineInterpolatePoint(linegeo, (housenumber_for_place-startnumber::float)/(endnumber-startnumber)::float) "; + $sSQL .= " ELSE ST_LineInterpolatePoint(linegeo, 0.5) END as centroid"; + $sSQL .= ", parent_place_id, housenumber_for_place "; + $sSQL .= " from (location_property_osmline "; + $sSQL .= " join (values ".$sHousenumbers.") as housenumbers(place_id, housenumber_for_place) using(place_id)) "; + $sSQL .= " where housenumber_for_place>=0 and 30 between $this->iMinAddressRank and $this->iMaxAddressRank) as blub"; //postgres wants an alias here + $sSQL .= " group by place_id, housenumber_for_place, calculated_country_code "; //is this group by really needed?, place_id + housenumber (in combination) are unique + if (!$this->bDeDupe) $sSQL .= ", place_id "; + + if (CONST_Use_Aux_Location_data) { + $sSQL .= " union "; + $sSQL .= "select 'L' as osm_type, place_id as osm_id, 'place' as class, 'house' as type, null as admin_level, 0 as rank_search, 0 as rank_address, min(place_id) as place_id, min(parent_place_id) as parent_place_id, 'us' as country_code, "; + $sSQL .= "get_address_by_language(place_id, -1, $sLanguagePrefArraySQL) as langaddress, "; + $sSQL .= "null as placename, "; + $sSQL .= "null as ref, "; + if ($this->bIncludeExtraTags) $sSQL .= "null as extra, "; + if ($this->bIncludeNameDetails) $sSQL .= "null as names, "; + $sSQL .= "avg(ST_X(centroid)) as lon, avg(ST_Y(centroid)) as lat, "; + $sSQL .= $sImportanceSQL."-1.10 as importance, "; + $sSQL .= "(select max(p.importance*(p.rank_address+2)) from place_addressline s, placex p where s.place_id = min(location_property_aux.parent_place_id) and p.place_id = s.address_place_id and s.isaddress and p.importance is not null) as addressimportance, "; + $sSQL .= "null as extra_place "; + $sSQL .= "from location_property_aux where place_id in ($sPlaceIDs) "; + $sSQL .= "and 30 between $this->iMinAddressRank and $this->iMaxAddressRank "; + $sSQL .= "group by place_id"; + if (!$this->bDeDupe) $sSQL .= ", place_id"; + $sSQL .= ", get_address_by_language(place_id, -1, $sLanguagePrefArraySQL) "; + } + } + + $sSQL .= " order by importance desc"; + if (CONST_Debug) { + echo "
"; + var_dump($sSQL); + } + $aSearchResults = chksql( + $this->oDB->getAll($sSQL), + "Could not get details for place." + ); + + return $aSearchResults; + } + + public function getGroupedSearches($aSearches, $aPhraseTypes, $aPhrases, $aValidTokens, $aWordFrequencyScores, $bStructuredPhrases) + { + /* + 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) { + // Too many permutations - too expensive + if ($iWordSet > 120) break; + + $aWordsetSearches = $aSearches; + + // Add all words from this wordset + foreach ($aWordset as $iToken => $sToken) { + //echo "
$sToken"; + $aNewWordsetSearches = array(); + + foreach ($aWordsetSearches as $aCurrentSearch) { + //echo ""; + //var_dump($aCurrentSearch); + //echo ""; + + // If the token is valid + if (isset($aValidTokens[' '.$sToken])) { + foreach ($aValidTokens[' '.$sToken] as $aSearchTerm) { + $aSearch = $aCurrentSearch; + $aSearch['iSearchRank']++; + if (($sPhraseType == '' || $sPhraseType == 'country') && !empty($aSearchTerm['country_code']) && $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 (optimisation) + if (($iToken+1 != sizeof($aWordset) || $iPhrase+1 != sizeof($aPhrases))) { + $aSearch['iSearchRank'] += 5; + } + if ($aSearch['iSearchRank'] < $this->iMaxRank) $aNewWordsetSearches[] = $aSearch; + } + } elseif (isset($aSearchTerm['lat']) && $aSearchTerm['lat'] !== '' && $aSearchTerm['lat'] !== null) { + if ($aSearch['fLat'] === '') { + $aSearch['fLat'] = $aSearchTerm['lat']; + $aSearch['fLon'] = $aSearchTerm['lon']; + $aSearch['fRadius'] = $aSearchTerm['radius']; + if ($aSearch['iSearchRank'] < $this->iMaxRank) $aNewWordsetSearches[] = $aSearch; + } + } elseif ($sPhraseType == 'postalcode') { + // 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 + if (isset($aSearchTerm['word_id']) && $aSearchTerm['word_id']) { + // If we already have a name try putting the postcode first + if (sizeof($aSearch['aName'])) { + $aNewSearch = $aSearch; + $aNewSearch['aAddress'] = array_merge($aNewSearch['aAddress'], $aNewSearch['aName']); + $aNewSearch['aName'] = array(); + $aNewSearch['aName'][$aSearchTerm['word_id']] = $aSearchTerm['word_id']; + if ($aSearch['iSearchRank'] < $this->iMaxRank) $aNewWordsetSearches[] = $aNewSearch; + } + + if (sizeof($aSearch['aName'])) { + if ((!$bStructuredPhrases || $iPhrase > 0) && $sPhraseType != 'country' && (!isset($aValidTokens[$sToken]) || strpos($sToken, ' ') !== false)) { + $aSearch['aAddress'][$aSearchTerm['word_id']] = $aSearchTerm['word_id']; + } else { + $aCurrentSearch['aFullNameAddress'][$aSearchTerm['word_id']] = $aSearchTerm['word_id']; + $aSearch['iSearchRank'] += 1000; // skip; + } + } else { + $aSearch['aName'][$aSearchTerm['word_id']] = $aSearchTerm['word_id']; + //$aSearch['iNamePhrase'] = $iPhrase; + } + if ($aSearch['iSearchRank'] < $this->iMaxRank) $aNewWordsetSearches[] = $aSearch; + } + } elseif (($sPhraseType == '' || $sPhraseType == 'street') && $aSearchTerm['class'] == 'place' && $aSearchTerm['type'] == 'house') { + if ($aSearch['sHouseNumber'] === '') { + $aSearch['sHouseNumber'] = $sToken; + // sanity check: if the housenumber is not mainly made + // up of numbers, add a penalty + if (preg_match_all("/[^0-9]/", $sToken, $aMatches) > 2) $aSearch['iSearchRank']++; + // also housenumbers should appear in the first or second phrase + if ($iPhrase > 1) $aSearch['iSearchRank'] += 1; + if ($aSearch['iSearchRank'] < $this->iMaxRank) $aNewWordsetSearches[] = $aSearch; + /* + // Fall back to not searching for this item (better than nothing) + $aSearch = $aCurrentSearch; + $aSearch['iSearchRank'] += 1; + if ($aSearch['iSearchRank'] < $this->iMaxRank) $aNewWordsetSearches[] = $aSearch; + */ + } + } elseif ($sPhraseType == '' && $aSearchTerm['class'] !== '' && $aSearchTerm['class'] !== null) { + if ($aSearch['sClass'] === '') { + $aSearch['sOperator'] = $aSearchTerm['operator']; + $aSearch['sClass'] = $aSearchTerm['class']; + $aSearch['sType'] = $aSearchTerm['type']; + if (sizeof($aSearch['aName'])) $aSearch['sOperator'] = 'name'; + else $aSearch['sOperator'] = 'near'; // near = in for the moment + if (strlen($aSearchTerm['operator']) == 0) $aSearch['iSearchRank'] += 1; + + if ($aSearch['iSearchRank'] < $this->iMaxRank) $aNewWordsetSearches[] = $aSearch; + } + } elseif (isset($aSearchTerm['word_id']) && $aSearchTerm['word_id']) { + if (sizeof($aSearch['aName'])) { + if ((!$bStructuredPhrases || $iPhrase > 0) && $sPhraseType != 'country' && (!isset($aValidTokens[$sToken]) || strpos($sToken, ' ') !== false)) { + $aSearch['aAddress'][$aSearchTerm['word_id']] = $aSearchTerm['word_id']; + } else { + $aCurrentSearch['aFullNameAddress'][$aSearchTerm['word_id']] = $aSearchTerm['word_id']; + $aSearch['iSearchRank'] += 1000; // skip; + } + } else { + $aSearch['aName'][$aSearchTerm['word_id']] = $aSearchTerm['word_id']; + //$aSearch['iNamePhrase'] = $iPhrase; + } + if ($aSearch['iSearchRank'] < $this->iMaxRank) $aNewWordsetSearches[] = $aSearch; + } + } + } + // Look for partial matches. + // Note that there is no point in adding country terms here + // because country are omitted in the address. + if (isset($aValidTokens[$sToken]) && $sPhraseType != 'country') { + // Allow searching for a word - but at extra cost + foreach ($aValidTokens[$sToken] as $aSearchTerm) { + if (isset($aSearchTerm['word_id']) && $aSearchTerm['word_id']) { + if ((!$bStructuredPhrases || $iPhrase > 0) && sizeof($aCurrentSearch['aName']) && strpos($sToken, ' ') === false) { + $aSearch = $aCurrentSearch; + $aSearch['iSearchRank'] += 1; + if ($aWordFrequencyScores[$aSearchTerm['word_id']] < CONST_Max_Word_Frequency) { + $aSearch['aAddress'][$aSearchTerm['word_id']] = $aSearchTerm['word_id']; + if ($aSearch['iSearchRank'] < $this->iMaxRank) $aNewWordsetSearches[] = $aSearch; + } elseif (isset($aValidTokens[' '.$sToken])) { // revert to the token version? + $aSearch['aAddressNonSearch'][$aSearchTerm['word_id']] = $aSearchTerm['word_id']; + $aSearch['iSearchRank'] += 1; + if ($aSearch['iSearchRank'] < $this->iMaxRank) $aNewWordsetSearches[] = $aSearch; + foreach ($aValidTokens[' '.$sToken] as $aSearchTermToken) { + if (empty($aSearchTermToken['country_code']) + && empty($aSearchTermToken['lat']) + && empty($aSearchTermToken['class']) + ) { + $aSearch = $aCurrentSearch; + $aSearch['iSearchRank'] += 1; + $aSearch['aAddress'][$aSearchTermToken['word_id']] = $aSearchTermToken['word_id']; + if ($aSearch['iSearchRank'] < $this->iMaxRank) $aNewWordsetSearches[] = $aSearch; + } + } + } else { + $aSearch['aAddressNonSearch'][$aSearchTerm['word_id']] = $aSearchTerm['word_id']; + if (preg_match('#^[0-9]+$#', $sToken)) $aSearch['iSearchRank'] += 2; + if ($aSearch['iSearchRank'] < $this->iMaxRank) $aNewWordsetSearches[] = $aSearch; + } + } + + if (!sizeof($aCurrentSearch['aName']) || $aCurrentSearch['iNamePhrase'] == $iPhrase) { + $aSearch = $aCurrentSearch; + $aSearch['iSearchRank'] += 1; + if (!sizeof($aCurrentSearch['aName'])) $aSearch['iSearchRank'] += 1; + if (preg_match('#^[0-9]+$#', $sToken)) $aSearch['iSearchRank'] += 2; + if ($aWordFrequencyScores[$aSearchTerm['word_id']] < CONST_Max_Word_Frequency) { + $aSearch['aName'][$aSearchTerm['word_id']] = $aSearchTerm['word_id']; + } else { + $aSearch['aNameNonSearch'][$aSearchTerm['word_id']] = $aSearchTerm['word_id']; + } + $aSearch['iNamePhrase'] = $iPhrase; + if ($aSearch['iSearchRank'] < $this->iMaxRank) $aNewWordsetSearches[] = $aSearch; + } + } + } + } else { + // Allow skipping a word - but at EXTREAM cost + //$aSearch = $aCurrentSearch; + //$aSearch['iSearchRank']+=100; + //$aNewWordsetSearches[] = $aSearch; + } + } + // Sort and cut + usort($aNewWordsetSearches, 'bySearchRank'); + $aWordsetSearches = array_slice($aNewWordsetSearches, 0, 50); + } + //var_Dump('
',sizeof($aWordsetSearches)); exit; + + $aNewPhraseSearches = array_merge($aNewPhraseSearches, $aNewWordsetSearches); + usort($aNewPhraseSearches, 'bySearchRank'); + + $aSearchHash = array(); + foreach ($aNewPhraseSearches as $iSearch => $aSearch) { + $sHash = serialize($aSearch); + if (isset($aSearchHash[$sHash])) unset($aNewPhraseSearches[$iSearch]); + else $aSearchHash[$sHash] = 1; + } + + $aNewPhraseSearches = array_slice($aNewPhraseSearches, 0, 50); + } + + // Re-group the searches by their score, junk anything over 20 as just not worth trying + $aGroupedSearches = array(); + foreach ($aNewPhraseSearches as $aSearch) { + if ($aSearch['iSearchRank'] < $this->iMaxRank) { + if (!isset($aGroupedSearches[$aSearch['iSearchRank']])) $aGroupedSearches[$aSearch['iSearchRank']] = array(); + $aGroupedSearches[$aSearch['iSearchRank']][] = $aSearch; + } + } + ksort($aGroupedSearches); + + $iSearchCount = 0; + $aSearches = array(); + foreach ($aGroupedSearches as $iScore => $aNewSearches) { + $iSearchCount += sizeof($aNewSearches); + $aSearches = array_merge($aSearches, $aNewSearches); + if ($iSearchCount > 50) break; + } + + //if (CONST_Debug) _debugDumpGroupedSearches($aGroupedSearches, $aValidTokens); + } + return $aGroupedSearches; + } + + /* Perform the actual query lookup. + + Returns an ordered list of results, each with the following fields: + osm_type: type of corresponding OSM object + N - node + W - way + R - relation + P - postcode (internally computed) + osm_id: id of corresponding OSM object + class: general object class (corresponds to tag key of primary OSM tag) + type: subclass of object (corresponds to tag value of primary OSM tag) + admin_level: see http://wiki.openstreetmap.org/wiki/Admin_level + rank_search: rank in search hierarchy + (see also http://wiki.openstreetmap.org/wiki/Nominatim/Development_overview#Country_to_street_level) + rank_address: rank in address hierarchy (determines orer in address) + place_id: internal key (may differ between different instances) + country_code: ISO country code + langaddress: localized full address + placename: localized name of object + ref: content of ref tag (if available) + lon: longitude + lat: latitude + importance: importance of place based on Wikipedia link count + addressimportance: cumulated importance of address elements + extra_place: type of place (for admin boundaries, if there is a place tag) + aBoundingBox: bounding Box + label: short description of the object class/type (English only) + name: full name (currently the same as langaddress) + foundorder: secondary ordering for places with same importance + */ + + + public function lookup() + { + if (!$this->sQuery && !$this->aStructuredQuery) return false; + + $sLanguagePrefArraySQL = "ARRAY[".join(',', array_map("getDBQuoted", $this->aLangPrefOrder))."]"; + $sCountryCodesSQL = false; + if ($this->aCountryCodes) { + $sCountryCodesSQL = join(',', array_map('addQuotes', $this->aCountryCodes)); + } + + $sQuery = $this->sQuery; + + // Conflicts between US state abreviations and various words for 'the' in different languages + if (isset($this->aLangPrefOrder['name:en'])) { + $sQuery = preg_replace('/(^|,)\s*il\s*(,|$)/', '\1illinois\2', $sQuery); + $sQuery = preg_replace('/(^|,)\s*al\s*(,|$)/', '\1alabama\2', $sQuery); + $sQuery = preg_replace('/(^|,)\s*la\s*(,|$)/', '\1louisiana\2', $sQuery); + } + + $bBoundingBoxSearch = $this->bBoundedSearch && $this->sViewboxSmallSQL; + if ($this->sViewboxCentreSQL) { + // For complex viewboxes (routes) precompute the bounding geometry + $sGeom = chksql( + $this->oDB->getOne("select ".$this->sViewboxSmallSQL), + "Could not get small viewbox" + ); + $this->sViewboxSmallSQL = "'".$sGeom."'::geometry"; + + $sGeom = chksql( + $this->oDB->getOne("select ".$this->sViewboxLargeSQL), + "Could not get large viewbox" + ); + $this->sViewboxLargeSQL = "'".$sGeom."'::geometry"; + } + + // Do we have anything that looks like a lat/lon pair? + if ($aLooksLike = looksLikeLatLonPair($sQuery)) { + $this->setNearPoint(array($aLooksLike['lat'], $aLooksLike['lon'])); + $sQuery = $aLooksLike['query']; + } + + $aSearchResults = array(); + if ($sQuery || $this->aStructuredQuery) { + // Start with a blank search + $aSearches = array( + array( + 'iSearchRank' => 0, + 'iNamePhrase' => -1, + 'sCountryCode' => false, + 'aName' => array(), + 'aAddress' => array(), + 'aFullNameAddress' => array(), + 'aNameNonSearch' => array(), + 'aAddressNonSearch' => array(), + 'sOperator' => '', + 'aFeatureName' => array(), + 'sClass' => '', + 'sType' => '', + 'sHouseNumber' => '', + 'fLat' => '', + 'fLon' => '', + 'fRadius' => '' + ) + ); + + // Do we have a radius search? + $sNearPointSQL = false; + if ($this->aNearPoint) { + $sNearPointSQL = "ST_SetSRID(ST_Point(".(float)$this->aNearPoint[1].",".(float)$this->aNearPoint[0]."),4326)"; + $aSearches[0]['fLat'] = (float)$this->aNearPoint[0]; + $aSearches[0]['fLon'] = (float)$this->aNearPoint[1]; + $aSearches[0]['fRadius'] = (float)$this->aNearPoint[2]; + } + + // Any 'special' terms in the search? + $bSpecialTerms = false; + preg_match_all('/\\[(.*)=(.*)\\]/', $sQuery, $aSpecialTermsRaw, PREG_SET_ORDER); + $aSpecialTerms = array(); + foreach ($aSpecialTermsRaw as $aSpecialTerm) { + $sQuery = str_replace($aSpecialTerm[0], ' ', $sQuery); + $aSpecialTerms[strtolower($aSpecialTerm[1])] = $aSpecialTerm[2]; + } + + preg_match_all('/\\[([\\w ]*)\\]/u', $sQuery, $aSpecialTermsRaw, PREG_SET_ORDER); + $aSpecialTerms = array(); + if (isset($this->aStructuredQuery['amenity']) && $this->aStructuredQuery['amenity']) { + $aSpecialTermsRaw[] = array('['.$this->aStructuredQuery['amenity'].']', $this->aStructuredQuery['amenity']); + unset($this->aStructuredQuery['amenity']); + } + + foreach ($aSpecialTermsRaw as $aSpecialTerm) { + $sQuery = str_replace($aSpecialTerm[0], ' ', $sQuery); + $sToken = chksql($this->oDB->getOne("select make_standard_name('".$aSpecialTerm[1]."') as string")); + $sSQL = 'select * from (select word_id,word_token, word, class, type, country_code, operator'; + $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 = chksql($this->oDB->getAll($sSQL)); + $aNewSearches = array(); + foreach ($aSearches as $aSearch) { + foreach ($aSearchWords as $aSearchTerm) { + $aNewSearch = $aSearch; + if ($aSearchTerm['country_code']) { + $aNewSearch['sCountryCode'] = strtolower($aSearchTerm['country_code']); + $aNewSearches[] = $aNewSearch; + $bSpecialTerms = true; + } + if ($aSearchTerm['class']) { + $aNewSearch['sClass'] = $aSearchTerm['class']; + $aNewSearch['sType'] = $aSearchTerm['type']; + $aNewSearches[] = $aNewSearch; + $bSpecialTerms = true; + } + } + } + $aSearches = $aNewSearches; + } + + // Split query into phrases + // Commas are used to reduce the search space by indicating where phrases split + if ($this->aStructuredQuery) { + $aPhrases = $this->aStructuredQuery; + $bStructuredPhrases = true; + } else { + $aPhrases = explode(',', $sQuery); + $bStructuredPhrases = false; + } + + // Convert each phrase to standard form + // Create a list of standard words + // Get all 'sets' of words + // Generate a complete list of all + $aTokens = array(); + foreach ($aPhrases as $iPhrase => $sPhrase) { + $aPhrase = chksql( + $this->oDB->getRow("select make_standard_name('".pg_escape_string($sPhrase)."') as string"), + "Cannot nomralize query string (is it an UTF-8 string?)" + ); + if (trim($aPhrase['string'])) { + $aPhrases[$iPhrase] = $aPhrase; + $aPhrases[$iPhrase]['words'] = explode(' ', $aPhrases[$iPhrase]['string']); + $aPhrases[$iPhrase]['wordsets'] = getWordSets($aPhrases[$iPhrase]['words'], 0); + $aTokens = array_merge($aTokens, getTokensFromSets($aPhrases[$iPhrase]['wordsets'])); + } else { + unset($aPhrases[$iPhrase]); + } + } + + // Reindex phrases - we make assumptions later on that they are numerically keyed in order + $aPhraseTypes = array_keys($aPhrases); + $aPhrases = array_values($aPhrases); + + if (sizeof($aTokens)) { + // Check which tokens we have, get the ID numbers + $sSQL = 'select word_id,word_token, word, class, type, country_code, operator, search_name_count'; + $sSQL .= ' from word where word_token in ('.join(',', array_map("getDBQuoted", $aTokens)).')'; + + if (CONST_Debug) var_Dump($sSQL); + + $aValidTokens = array(); + if (sizeof($aTokens)) { + $aDatabaseWords = chksql( + $this->oDB->getAll($sSQL), + "Could not get word tokens." + ); + } else { + $aDatabaseWords = array(); + } + $aPossibleMainWordIDs = array(); + $aWordFrequencyScores = array(); + foreach ($aDatabaseWords as $aToken) { + // Very special case - require 2 letter country param to match the country code found + if ($bStructuredPhrases && $aToken['country_code'] && !empty($this->aStructuredQuery['country']) + && strlen($this->aStructuredQuery['country']) == 2 && strtolower($this->aStructuredQuery['country']) != $aToken['country_code'] + ) { + continue; + } + + if (isset($aValidTokens[$aToken['word_token']])) { + $aValidTokens[$aToken['word_token']][] = $aToken; + } else { + $aValidTokens[$aToken['word_token']] = array($aToken); + } + if (!$aToken['class'] && !$aToken['country_code']) $aPossibleMainWordIDs[$aToken['word_id']] = 1; + $aWordFrequencyScores[$aToken['word_id']] = $aToken['search_name_count'] + 1; + } + if (CONST_Debug) var_Dump($aPhrases, $aValidTokens); + + // Try and calculate GB postcodes we might be missing + foreach ($aTokens as $sToken) { + // Source of gb postcodes is now definitive - always use + if (preg_match('/^([A-Z][A-Z]?[0-9][0-9A-Z]? ?[0-9])([A-Z][A-Z])$/', strtoupper(trim($sToken)), $aData)) { + if (substr($aData[1], -2, 1) != ' ') { + $aData[0] = substr($aData[0], 0, strlen($aData[1])-1).' '.substr($aData[0], strlen($aData[1])-1); + $aData[1] = substr($aData[1], 0, -1).' '.substr($aData[1], -1, 1); + } + $aGBPostcodeLocation = gbPostcodeCalculate($aData[0], $aData[1], $aData[2], $this->oDB); + if ($aGBPostcodeLocation) { + $aValidTokens[$sToken] = $aGBPostcodeLocation; + } + } elseif (!isset($aValidTokens[$sToken]) && preg_match('/^([0-9]{5}) [0-9]{4}$/', $sToken, $aData)) { + // US ZIP+4 codes - if there is no token, + // merge in the 5-digit ZIP code + if (isset($aValidTokens[$aData[1]])) { + foreach ($aValidTokens[$aData[1]] as $aToken) { + if (!$aToken['class']) { + if (isset($aValidTokens[$sToken])) { + $aValidTokens[$sToken][] = $aToken; + } else { + $aValidTokens[$sToken] = array($aToken); + } + } + } + } + } + } + + foreach ($aTokens as $sToken) { + // Unknown single word token with a number - assume it is a house number + if (!isset($aValidTokens[' '.$sToken]) && strpos($sToken, ' ') === false && preg_match('/[0-9]/', $sToken)) { + $aValidTokens[' '.$sToken] = array(array('class' => 'place', 'type' => 'house')); + } + } + + // Any words that have failed completely? + // TODO: suggestions + + // Start the search process + // array with: placeid => -1 | tiger-housenumber + $aResultPlaceIDs = array(); + + $aGroupedSearches = $this->getGroupedSearches($aSearches, $aPhraseTypes, $aPhrases, $aValidTokens, $aWordFrequencyScores, $bStructuredPhrases); + + if ($this->bReverseInPlan) { + // Reverse phrase array and also reverse the order of the wordsets in + // the first and final phrase. Don't bother about phrases in the middle + // because order in the address doesn't matter. + $aPhrases = array_reverse($aPhrases); + $aPhrases[0]['wordsets'] = getInverseWordSets($aPhrases[0]['words'], 0); + if (sizeof($aPhrases) > 1) { + $aFinalPhrase = end($aPhrases); + $aPhrases[sizeof($aPhrases)-1]['wordsets'] = getInverseWordSets($aFinalPhrase['words'], 0); + } + $aReverseGroupedSearches = $this->getGroupedSearches($aSearches, null, $aPhrases, $aValidTokens, $aWordFrequencyScores, false); + + foreach ($aGroupedSearches as $aSearches) { + foreach ($aSearches as $aSearch) { + if ($aSearch['iSearchRank'] < $this->iMaxRank) { + if (!isset($aReverseGroupedSearches[$aSearch['iSearchRank']])) $aReverseGroupedSearches[$aSearch['iSearchRank']] = array(); + $aReverseGroupedSearches[$aSearch['iSearchRank']][] = $aSearch; + } + } + } + + $aGroupedSearches = $aReverseGroupedSearches; + ksort($aGroupedSearches); + } + } else { + // Re-group the searches by their score, junk anything over 20 as just not worth trying + $aGroupedSearches = array(); + foreach ($aSearches as $aSearch) { + if ($aSearch['iSearchRank'] < $this->iMaxRank) { + if (!isset($aGroupedSearches[$aSearch['iSearchRank']])) $aGroupedSearches[$aSearch['iSearchRank']] = array(); + $aGroupedSearches[$aSearch['iSearchRank']][] = $aSearch; + } + } + ksort($aGroupedSearches); + } + + if (CONST_Debug) var_Dump($aGroupedSearches); + if (CONST_Search_TryDroppedAddressTerms && sizeof($this->aStructuredQuery) > 0) { + $aCopyGroupedSearches = $aGroupedSearches; + foreach ($aCopyGroupedSearches as $iGroup => $aSearches) { + foreach ($aSearches as $iSearch => $aSearch) { + $aReductionsList = array($aSearch['aAddress']); + $iSearchRank = $aSearch['iSearchRank']; + while (sizeof($aReductionsList) > 0) { + $iSearchRank += 5; + if ($iSearchRank > iMaxRank) break 3; + $aNewReductionsList = array(); + foreach ($aReductionsList as $aReductionsWordList) { + for ($iReductionWord = 0; $iReductionWord < sizeof($aReductionsWordList); $iReductionWord++) { + $aReductionsWordListResult = array_merge(array_slice($aReductionsWordList, 0, $iReductionWord), array_slice($aReductionsWordList, $iReductionWord+1)); + $aReverseSearch = $aSearch; + $aSearch['aAddress'] = $aReductionsWordListResult; + $aSearch['iSearchRank'] = $iSearchRank; + $aGroupedSearches[$iSearchRank][] = $aReverseSearch; + if (sizeof($aReductionsWordListResult) > 0) { + $aNewReductionsList[] = $aReductionsWordListResult; + } + } + } + $aReductionsList = $aNewReductionsList; + } + } + } + ksort($aGroupedSearches); + } + + // Filter out duplicate searches + $aSearchHash = array(); + foreach ($aGroupedSearches as $iGroup => $aSearches) { + foreach ($aSearches as $iSearch => $aSearch) { + $sHash = serialize($aSearch); + if (isset($aSearchHash[$sHash])) { + unset($aGroupedSearches[$iGroup][$iSearch]); + if (sizeof($aGroupedSearches[$iGroup]) == 0) unset($aGroupedSearches[$iGroup]); + } else { + $aSearchHash[$sHash] = 1; + } + } + } + + if (CONST_Debug) _debugDumpGroupedSearches($aGroupedSearches, $aValidTokens); + + $iGroupLoop = 0; + $iQueryLoop = 0; + foreach ($aGroupedSearches as $iGroupedRank => $aSearches) { + $iGroupLoop++; + foreach ($aSearches as $aSearch) { + $iQueryLoop++; + $searchedHousenumber = -1; + + if (CONST_Debug) echo "
Search Loop, group $iGroupLoop, loop $iQueryLoop"; + if (CONST_Debug) _debugDumpGroupedSearches(array($iGroupedRank => array($aSearch)), $aValidTokens); + + // No location term? + if (!sizeof($aSearch['aName']) && !sizeof($aSearch['aAddress']) && !$aSearch['fLon']) { + if ($aSearch['sCountryCode'] && !$aSearch['sClass'] && !$aSearch['sHouseNumber']) { + // Just looking for a country by code - look it up + if (4 >= $this->iMinAddressRank && 4 <= $this->iMaxAddressRank) { + $sSQL = "select place_id from placex where calculated_country_code='".$aSearch['sCountryCode']."' and rank_search = 4"; + if ($sCountryCodesSQL) $sSQL .= " and calculated_country_code in ($sCountryCodesSQL)"; + if ($bBoundingBoxSearch) + $sSQL .= " and _st_intersects($this->sViewboxSmallSQL, geometry)"; + $sSQL .= " order by st_area(geometry) desc limit 1"; + if (CONST_Debug) var_dump($sSQL); + $aPlaceIDs = chksql($this->oDB->getCol($sSQL)); + } else { + $aPlaceIDs = array(); + } + } else { + if (!$bBoundingBoxSearch && !$aSearch['fLon']) continue; + if (!$aSearch['sClass']) continue; + + $sSQL = "select count(*) from pg_tables where tablename = 'place_classtype_".$aSearch['sClass']."_".$aSearch['sType']."'"; + if (chksql($this->oDB->getOne($sSQL))) { + $sSQL = "select place_id from place_classtype_".$aSearch['sClass']."_".$aSearch['sType']." ct"; + if ($sCountryCodesSQL) $sSQL .= " join placex using (place_id)"; + $sSQL .= " where st_contains($this->sViewboxSmallSQL, ct.centroid)"; + if ($sCountryCodesSQL) $sSQL .= " and calculated_country_code in ($sCountryCodesSQL)"; + if (sizeof($this->aExcludePlaceIDs)) { + $sSQL .= " and place_id not in (".join(',', $this->aExcludePlaceIDs).")"; + } + if ($this->sViewboxCentreSQL) $sSQL .= " order by st_distance($this->sViewboxCentreSQL, ct.centroid) asc"; + $sSQL .= " limit $this->iLimit"; + if (CONST_Debug) var_dump($sSQL); + $aPlaceIDs = chksql($this->oDB->getCol($sSQL)); + + // If excluded place IDs are given, it is fair to assume that + // there have been results in the small box, so no further + // expansion in that case. + // Also don't expand if bounded results were requested. + if (!sizeof($aPlaceIDs) && !sizeof($this->aExcludePlaceIDs) && !$this->bBoundedSearch) { + $sSQL = "select place_id from place_classtype_".$aSearch['sClass']."_".$aSearch['sType']." ct"; + if ($sCountryCodesSQL) $sSQL .= " join placex using (place_id)"; + $sSQL .= " where st_contains($this->sViewboxLargeSQL, ct.centroid)"; + if ($sCountryCodesSQL) $sSQL .= " and calculated_country_code in ($sCountryCodesSQL)"; + if ($this->sViewboxCentreSQL) $sSQL .= " order by st_distance($this->sViewboxCentreSQL, ct.centroid) asc"; + $sSQL .= " limit $this->iLimit"; + if (CONST_Debug) var_dump($sSQL); + $aPlaceIDs = chksql($this->oDB->getCol($sSQL)); + } + } else { + $sSQL = "select place_id from placex where class='".$aSearch['sClass']."' and type='".$aSearch['sType']."'"; + $sSQL .= " and st_contains($this->sViewboxSmallSQL, geometry) and linked_place_id is null"; + if ($sCountryCodesSQL) $sSQL .= " and calculated_country_code in ($sCountryCodesSQL)"; + if ($this->sViewboxCentreSQL) $sSQL .= " order by st_distance($this->sViewboxCentreSQL, centroid) asc"; + $sSQL .= " limit $this->iLimit"; + if (CONST_Debug) var_dump($sSQL); + $aPlaceIDs = chksql($this->oDB->getCol($sSQL)); + } + } + } elseif ($aSearch['fLon'] && !sizeof($aSearch['aName']) && !sizeof($aSearch['aAddress']) && !$aSearch['sClass']) { + // If a coordinate is given, the search must either + // be for a name or a special search. Ignore everythin else. + $aPlaceIDs = array(); + } else { + $aPlaceIDs = array(); + + // First we need a position, either aName or fLat or both + $aTerms = array(); + $aOrder = array(); + + if ($aSearch['sHouseNumber'] && sizeof($aSearch['aAddress'])) { + $sHouseNumberRegex = '\\\\m'.$aSearch['sHouseNumber'].'\\\\M'; + $aOrder[] = ""; + $aOrder[0] = " (exists(select place_id from placex where parent_place_id = search_name.place_id"; + $aOrder[0] .= " and transliteration(housenumber) ~* E'".$sHouseNumberRegex."' limit 1) "; + // also housenumbers from interpolation lines table are needed + $aOrder[0] .= " or exists(select place_id from location_property_osmline where parent_place_id = search_name.place_id"; + $aOrder[0] .= " and ".intval($aSearch['sHouseNumber']).">=startnumber and ".intval($aSearch['sHouseNumber'])."<=endnumber limit 1))"; + $aOrder[0] .= " desc"; + } + + // TODO: filter out the pointless search terms (2 letter name tokens and less) + // they might be right - but they are just too darned expensive to run + if (sizeof($aSearch['aName'])) $aTerms[] = "name_vector @> ARRAY[".join($aSearch['aName'], ",")."]"; - if (sizeof($aSearch['aNameNonSearch'])) $aTerms[] = "array_cat(name_vector,ARRAY[]::integer[]) @> ARRAY[".join($aSearch['aNameNonSearch'], ",")."]"; ++ //if (sizeof($aSearch['aNameNonSearch'])) $aTerms[] = "array_cat(name_vector,ARRAY[]::integer[]) @> ARRAY[".join($aSearch['aNameNonSearch'], ",")."]"; + if (sizeof($aSearch['aAddress']) && $aSearch['aName'] != $aSearch['aAddress']) { + // For infrequent name terms disable index usage for address + if (CONST_Search_NameOnlySearchFrequencyThreshold + && sizeof($aSearch['aName']) == 1 + && $aWordFrequencyScores[$aSearch['aName'][reset($aSearch['aName'])]] < CONST_Search_NameOnlySearchFrequencyThreshold + ) { - $aTerms[] = "array_cat(nameaddress_vector,ARRAY[]::integer[]) @> ARRAY[".join(array_merge($aSearch['aAddress'], $aSearch['aAddressNonSearch']), ",")."]"; ++ //$aTerms[] = "array_cat(nameaddress_vector,ARRAY[]::integer[]) @> ARRAY[".join(array_merge($aSearch['aAddress'], $aSearch['aAddressNonSearch']), ",")."]"; + } else { + $aTerms[] = "nameaddress_vector @> ARRAY[".join($aSearch['aAddress'], ",")."]"; - if (sizeof($aSearch['aAddressNonSearch'])) { ++ /*if (sizeof($aSearch['aAddressNonSearch'])) { + $aTerms[] = "array_cat(nameaddress_vector,ARRAY[]::integer[]) @> ARRAY[".join($aSearch['aAddressNonSearch'], ",")."]"; - } ++ }*/ + } + } + if ($aSearch['sCountryCode']) $aTerms[] = "country_code = '".pg_escape_string($aSearch['sCountryCode'])."'"; + if ($aSearch['sHouseNumber']) { + $aTerms[] = "address_rank between 16 and 27"; + } else { + if ($this->iMinAddressRank > 0) { + $aTerms[] = "address_rank >= ".$this->iMinAddressRank; + } + if ($this->iMaxAddressRank < 30) { + $aTerms[] = "address_rank <= ".$this->iMaxAddressRank; + } + } + if ($aSearch['fLon'] && $aSearch['fLat']) { + $aTerms[] = "ST_DWithin(centroid, ST_SetSRID(ST_Point(".$aSearch['fLon'].",".$aSearch['fLat']."),4326), ".$aSearch['fRadius'].")"; + $aOrder[] = "ST_Distance(centroid, ST_SetSRID(ST_Point(".$aSearch['fLon'].",".$aSearch['fLat']."),4326)) ASC"; + } + if (sizeof($this->aExcludePlaceIDs)) { + $aTerms[] = "place_id not in (".join(',', $this->aExcludePlaceIDs).")"; + } + if ($sCountryCodesSQL) { + $aTerms[] = "country_code in ($sCountryCodesSQL)"; + } + + if ($bBoundingBoxSearch) $aTerms[] = "centroid && $this->sViewboxSmallSQL"; + if ($sNearPointSQL) $aOrder[] = "ST_Distance($sNearPointSQL, centroid) asc"; + + if ($aSearch['sHouseNumber']) { + $sImportanceSQL = '- abs(26 - address_rank) + 3'; + } else { + $sImportanceSQL = '(case when importance = 0 OR importance IS NULL then 0.75-(search_rank::float/40) else importance end)'; + } + if ($this->sViewboxSmallSQL) $sImportanceSQL .= " * case when ST_Contains($this->sViewboxSmallSQL, centroid) THEN 1 ELSE 0.5 END"; + if ($this->sViewboxLargeSQL) $sImportanceSQL .= " * case when ST_Contains($this->sViewboxLargeSQL, centroid) THEN 1 ELSE 0.5 END"; + + $aOrder[] = "$sImportanceSQL DESC"; + if (sizeof($aSearch['aFullNameAddress'])) { + $sExactMatchSQL = '(select count(*) from (select unnest(ARRAY['.join($aSearch['aFullNameAddress'], ",").']) INTERSECT select unnest(nameaddress_vector))s) as exactmatch'; + $aOrder[] = 'exactmatch DESC'; + } else { + $sExactMatchSQL = '0::int as exactmatch'; + } + + if (sizeof($aTerms)) { + $sSQL = "select place_id, "; + $sSQL .= $sExactMatchSQL; + $sSQL .= " from search_name"; + $sSQL .= " where ".join(' and ', $aTerms); + $sSQL .= " order by ".join(', ', $aOrder); + if ($aSearch['sHouseNumber'] || $aSearch['sClass']) { + $sSQL .= " limit 20"; + } elseif (!sizeof($aSearch['aName']) && !sizeof($aSearch['aAddress']) && $aSearch['sClass']) { + $sSQL .= " limit 1"; + } else { + $sSQL .= " limit ".$this->iLimit; + } + + if (CONST_Debug) var_dump($sSQL); + $aViewBoxPlaceIDs = chksql( + $this->oDB->getAll($sSQL), + "Could not get places for search terms." + ); + //var_dump($aViewBoxPlaceIDs); + // Did we have an viewbox matches? + $aPlaceIDs = array(); + $bViewBoxMatch = false; + foreach ($aViewBoxPlaceIDs as $aViewBoxRow) { + //if ($bViewBoxMatch == 1 && $aViewBoxRow['in_small'] == 'f') break; + //if ($bViewBoxMatch == 2 && $aViewBoxRow['in_large'] == 'f') break; + //if ($aViewBoxRow['in_small'] == 't') $bViewBoxMatch = 1; + //else if ($aViewBoxRow['in_large'] == 't') $bViewBoxMatch = 2; + $aPlaceIDs[] = $aViewBoxRow['place_id']; + $this->exactMatchCache[$aViewBoxRow['place_id']] = $aViewBoxRow['exactmatch']; + } + } + //var_Dump($aPlaceIDs); + //exit; + + //now search for housenumber, if housenumber provided + if ($aSearch['sHouseNumber'] && sizeof($aPlaceIDs)) { + $searchedHousenumber = intval($aSearch['sHouseNumber']); + $aRoadPlaceIDs = $aPlaceIDs; + $sPlaceIDs = join(',', $aPlaceIDs); + + // Now they are indexed, look for a house attached to a street we found + $sHouseNumberRegex = '\\\\m'.$aSearch['sHouseNumber'].'\\\\M'; + $sSQL = "select place_id from placex where parent_place_id in (".$sPlaceIDs.") and transliteration(housenumber) ~* E'".$sHouseNumberRegex."'"; + if (sizeof($this->aExcludePlaceIDs)) { + $sSQL .= " and place_id not in (".join(',', $this->aExcludePlaceIDs).")"; + } + $sSQL .= " limit $this->iLimit"; + if (CONST_Debug) var_dump($sSQL); + $aPlaceIDs = chksql($this->oDB->getCol($sSQL)); + + // if nothing found, search in the interpolation line table + if (!sizeof($aPlaceIDs)) { + // do we need to use transliteration and the regex for housenumbers??? + //new query for lines, not housenumbers anymore + if ($searchedHousenumber%2 == 0) { + //if housenumber is even, look for housenumber in streets with interpolationtype even or all + $sSQL = "select distinct place_id from location_property_osmline where parent_place_id in (".$sPlaceIDs.") and (interpolationtype='even' or interpolationtype='all') and ".$searchedHousenumber.">=startnumber and ".$searchedHousenumber."<=endnumber"; + } else { + //look for housenumber in streets with interpolationtype odd or all + $sSQL = "select distinct place_id from location_property_osmline where parent_place_id in (".$sPlaceIDs.") and (interpolationtype='odd' or interpolationtype='all') and ".$searchedHousenumber.">=startnumber and ".$searchedHousenumber."<=endnumber"; + } + + if (sizeof($this->aExcludePlaceIDs)) { + $sSQL .= " and place_id not in (".join(',', $this->aExcludePlaceIDs).")"; + } + //$sSQL .= " limit $this->iLimit"; + if (CONST_Debug) var_dump($sSQL); + //get place IDs + $aPlaceIDs = chksql($this->oDB->getCol($sSQL, 0)); + } + + // If nothing found try the aux fallback table + if (CONST_Use_Aux_Location_data && !sizeof($aPlaceIDs)) { + $sSQL = "select place_id from location_property_aux where parent_place_id in (".$sPlaceIDs.") and housenumber = '".pg_escape_string($aSearch['sHouseNumber'])."'"; + if (sizeof($this->aExcludePlaceIDs)) { + $sSQL .= " and parent_place_id not in (".join(',', $this->aExcludePlaceIDs).")"; + } + //$sSQL .= " limit $this->iLimit"; + if (CONST_Debug) var_dump($sSQL); + $aPlaceIDs = chksql($this->oDB->getCol($sSQL)); + } + + //if nothing was found in placex or location_property_aux, then search in Tiger data for this housenumber(location_property_tiger) + if (CONST_Use_US_Tiger_Data && !sizeof($aPlaceIDs)) { + //new query for lines, not housenumbers anymore + if ($searchedHousenumber%2 == 0) { + //if housenumber is even, look for housenumber in streets with interpolationtype even or all + $sSQL = "select distinct place_id from location_property_tiger where parent_place_id in (".$sPlaceIDs.") and (interpolationtype='even' or interpolationtype='all') and ".$searchedHousenumber.">=startnumber and ".$searchedHousenumber."<=endnumber"; + } else { + //look for housenumber in streets with interpolationtype odd or all + $sSQL = "select distinct place_id from location_property_tiger where parent_place_id in (".$sPlaceIDs.") and (interpolationtype='odd' or interpolationtype='all') and ".$searchedHousenumber.">=startnumber and ".$searchedHousenumber."<=endnumber"; + } + + if (sizeof($this->aExcludePlaceIDs)) { + $sSQL .= " and place_id not in (".join(',', $this->aExcludePlaceIDs).")"; + } + //$sSQL .= " limit $this->iLimit"; + if (CONST_Debug) var_dump($sSQL); + //get place IDs + $aPlaceIDs = chksql($this->oDB->getCol($sSQL, 0)); + } + + // Fallback to the road (if no housenumber was found) + if (!sizeof($aPlaceIDs) && preg_match('/[0-9]+/', $aSearch['sHouseNumber'])) { + $aPlaceIDs = $aRoadPlaceIDs; + //set to -1, if no housenumbers were found + $searchedHousenumber = -1; + } + //else: housenumber was found, remains saved in searchedHousenumber + } + + + if ($aSearch['sClass'] && sizeof($aPlaceIDs)) { + $sPlaceIDs = join(',', $aPlaceIDs); + $aClassPlaceIDs = array(); + + if (!$aSearch['sOperator'] || $aSearch['sOperator'] == 'name') { + // 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 calculated_country_code in ($sCountryCodesSQL)"; + $sSQL .= " order by rank_search asc limit $this->iLimit"; + if (CONST_Debug) var_dump($sSQL); + $aClassPlaceIDs = chksql($this->oDB->getCol($sSQL)); + } + + if (!$aSearch['sOperator'] || $aSearch['sOperator'] == 'near') { // & in + $sSQL = "select count(*) from pg_tables where tablename = 'place_classtype_".$aSearch['sClass']."_".$aSearch['sType']."'"; + $bCacheTable = chksql($this->oDB->getOne($sSQL)); + + $sSQL = "select min(rank_search) from placex where place_id in ($sPlaceIDs)"; + + if (CONST_Debug) var_dump($sSQL); + $this->iMaxRank = ((int)chksql($this->oDB->getOne($sSQL))); + + // For state / country level searches the normal radius search doesn't work very well + $sPlaceGeom = false; + if ($this->iMaxRank < 9 && $bCacheTable) { + // Try and get a polygon to search in instead + $sSQL = "select geometry from placex where place_id in ($sPlaceIDs) and rank_search < $this->iMaxRank + 5 and st_geometrytype(geometry) in ('ST_Polygon','ST_MultiPolygon') order by rank_search asc limit 1"; + if (CONST_Debug) var_dump($sSQL); + $sPlaceGeom = chksql($this->oDB->getOne($sSQL)); + } + + if ($sPlaceGeom) { + $sPlaceIDs = false; + } else { + $this->iMaxRank += 5; + $sSQL = "select place_id from placex where place_id in ($sPlaceIDs) and rank_search < $this->iMaxRank"; + if (CONST_Debug) var_dump($sSQL); + $aPlaceIDs = chksql($this->oDB->getCol($sSQL)); + $sPlaceIDs = join(',', $aPlaceIDs); + } + + if ($sPlaceIDs || $sPlaceGeom) { + $fRange = 0.01; + if ($bCacheTable) { + // More efficient - can make the range bigger + $fRange = 0.05; + + $sOrderBySQL = ''; + if ($sNearPointSQL) $sOrderBySQL = "ST_Distance($sNearPointSQL, l.centroid)"; + elseif ($sPlaceIDs) $sOrderBySQL = "ST_Distance(l.centroid, f.geometry)"; + elseif ($sPlaceGeom) $sOrderBysSQL = "ST_Distance(st_centroid('".$sPlaceGeom."'), l.centroid)"; + + $sSQL = "select distinct l.place_id".($sOrderBySQL?','.$sOrderBySQL:'')." from place_classtype_".$aSearch['sClass']."_".$aSearch['sType']." as l"; + if ($sCountryCodesSQL) $sSQL .= " join placex as lp using (place_id)"; + if ($sPlaceIDs) { + $sSQL .= ",placex as f where "; + $sSQL .= "f.place_id in ($sPlaceIDs) and ST_DWithin(l.centroid, f.centroid, $fRange) "; + } + if ($sPlaceGeom) { + $sSQL .= " where "; + $sSQL .= "ST_Contains('".$sPlaceGeom."', l.centroid) "; + } + if (sizeof($this->aExcludePlaceIDs)) { + $sSQL .= " and l.place_id not in (".join(',', $this->aExcludePlaceIDs).")"; + } + if ($sCountryCodesSQL) $sSQL .= " and lp.calculated_country_code in ($sCountryCodesSQL)"; + if ($sOrderBySQL) $sSQL .= "order by ".$sOrderBySQL." asc"; + if ($this->iOffset) $sSQL .= " offset $this->iOffset"; + $sSQL .= " limit $this->iLimit"; + if (CONST_Debug) var_dump($sSQL); + $aClassPlaceIDs = array_merge($aClassPlaceIDs, chksql($this->oDB->getCol($sSQL))); + } else { + if (isset($aSearch['fRadius']) && $aSearch['fRadius']) $fRange = $aSearch['fRadius']; + + $sOrderBySQL = ''; + if ($sNearPointSQL) $sOrderBySQL = "ST_Distance($sNearPointSQL, l.geometry)"; + else $sOrderBySQL = "ST_Distance(l.geometry, f.geometry)"; + + $sSQL = "select distinct l.place_id".($sOrderBysSQL?','.$sOrderBysSQL:'')." from placex as l,placex as f where "; + $sSQL .= "f.place_id in ( $sPlaceIDs) and ST_DWithin(l.geometry, f.centroid, $fRange) "; + $sSQL .= "and l.class='".$aSearch['sClass']."' and l.type='".$aSearch['sType']."' "; + if (sizeof($this->aExcludePlaceIDs)) { + $sSQL .= " and l.place_id not in (".join(',', $this->aExcludePlaceIDs).")"; + } + if ($sCountryCodesSQL) $sSQL .= " and l.calculated_country_code in ($sCountryCodesSQL)"; + if ($sOrderBy) $sSQL .= "order by ".$OrderBysSQL." asc"; + if ($this->iOffset) $sSQL .= " offset $this->iOffset"; + $sSQL .= " limit $this->iLimit"; + if (CONST_Debug) var_dump($sSQL); + $aClassPlaceIDs = array_merge($aClassPlaceIDs, chksql($this->oDB->getCol($sSQL))); + } + } + } + $aPlaceIDs = $aClassPlaceIDs; + } + } + + if (CONST_Debug) { + echo "
Place IDs: "; + var_Dump($aPlaceIDs); + } + + foreach ($aPlaceIDs as $iPlaceID) { + // array for placeID => -1 | Tiger housenumber + $aResultPlaceIDs[$iPlaceID] = $searchedHousenumber; + } + if ($iQueryLoop > 20) break; + } + + if (isset($aResultPlaceIDs) && sizeof($aResultPlaceIDs) && ($this->iMinAddressRank != 0 || $this->iMaxAddressRank != 30)) { + // Need to verify passes rank limits before dropping out of the loop (yuk!) + // reduces the number of place ids, like a filter + // rank_address is 30 for interpolated housenumbers + $sSQL = "select place_id from placex where place_id in (".join(',', array_keys($aResultPlaceIDs)).") "; + $sSQL .= "and (placex.rank_address between $this->iMinAddressRank and $this->iMaxAddressRank "; + if (14 >= $this->iMinAddressRank && 14 <= $this->iMaxAddressRank) $sSQL .= " OR (extratags->'place') = 'city'"; + if ($this->aAddressRankList) $sSQL .= " OR placex.rank_address in (".join(',', $this->aAddressRankList).")"; + if (CONST_Use_US_Tiger_Data) { + $sSQL .= ") UNION select place_id from location_property_tiger where place_id in (".join(',', array_keys($aResultPlaceIDs)).") "; + $sSQL .= "and (30 between $this->iMinAddressRank and $this->iMaxAddressRank "; + if ($this->aAddressRankList) $sSQL .= " OR 30 in (".join(',', $this->aAddressRankList).")"; + } + $sSQL .= ") UNION select place_id from location_property_osmline where place_id in (".join(',', array_keys($aResultPlaceIDs)).")"; + $sSQL .= " and (30 between $this->iMinAddressRank and $this->iMaxAddressRank)"; + if (CONST_Debug) var_dump($sSQL); + $aFilteredPlaceIDs = chksql($this->oDB->getCol($sSQL)); + $tempIDs = array(); + foreach ($aFilteredPlaceIDs as $placeID) { + $tempIDs[$placeID] = $aResultPlaceIDs[$placeID]; //assign housenumber to placeID + } + $aResultPlaceIDs = $tempIDs; + } + + //exit; + if (isset($aResultPlaceIDs) && sizeof($aResultPlaceIDs)) break; + if ($iGroupLoop > 4) break; + if ($iQueryLoop > 30) break; + } + + // Did we find anything? + if (isset($aResultPlaceIDs) && sizeof($aResultPlaceIDs)) { + $aSearchResults = $this->getDetails($aResultPlaceIDs); + } + } else { + // Just interpret as a reverse geocode + $oReverse = new Nominatim\ReverseGeocode($this->oDB); + $oReverse->setZoom(18); + + $aLookup = $oReverse->lookup( + (float)$this->aNearPoint[0], + (float)$this->aNearPoint[1], + false + ); + + if (CONST_Debug) var_dump("Reverse search", $aLookup); + + if ($aLookup['place_id']) { + $aSearchResults = $this->getDetails(array($aLookup['place_id'] => -1)); + } else { + $aSearchResults = array(); + } + } + + // No results? Done + if (!sizeof($aSearchResults)) { + if ($this->bFallback) { + if ($this->fallbackStructuredQuery()) { + return $this->lookup(); + } + } + + return array(); + } + + $aClassType = getClassTypesWithImportance(); + $aRecheckWords = preg_split('/\b[\s,\\-]*/u', $sQuery); + foreach ($aRecheckWords as $i => $sWord) { + if (!preg_match('/\pL/', $sWord)) unset($aRecheckWords[$i]); + } + + if (CONST_Debug) { + echo 'Recheck words:<\i>'; + var_dump($aRecheckWords); + } + + $oPlaceLookup = new PlaceLookup($this->oDB); + $oPlaceLookup->setIncludePolygonAsPoints($this->bIncludePolygonAsPoints); + $oPlaceLookup->setIncludePolygonAsText($this->bIncludePolygonAsText); + $oPlaceLookup->setIncludePolygonAsGeoJSON($this->bIncludePolygonAsGeoJSON); + $oPlaceLookup->setIncludePolygonAsKML($this->bIncludePolygonAsKML); + $oPlaceLookup->setIncludePolygonAsSVG($this->bIncludePolygonAsSVG); + $oPlaceLookup->setPolygonSimplificationThreshold($this->fPolygonSimplificationThreshold); + + foreach ($aSearchResults as $iResNum => $aResult) { + // Default + $fDiameter = getResultDiameter($aResult); + + $aOutlineResult = $oPlaceLookup->getOutlines($aResult['place_id'], $aResult['lon'], $aResult['lat'], $fDiameter/2); + if ($aOutlineResult) { + $aResult = array_merge($aResult, $aOutlineResult); + } + + if ($aResult['extra_place'] == 'city') { + $aResult['class'] = 'place'; + $aResult['type'] = 'city'; + $aResult['rank_search'] = 16; + } + + // Is there an icon set for this type of result? + if (isset($aClassType[$aResult['class'].':'.$aResult['type']]['icon']) + && $aClassType[$aResult['class'].':'.$aResult['type']]['icon'] + ) { + $aResult['icon'] = CONST_Website_BaseURL.'images/mapicons/'.$aClassType[$aResult['class'].':'.$aResult['type']]['icon'].'.p.20.png'; + } + + if (isset($aClassType[$aResult['class'].':'.$aResult['type'].':'.$aResult['admin_level']]['label']) + && $aClassType[$aResult['class'].':'.$aResult['type'].':'.$aResult['admin_level']]['label'] + ) { + $aResult['label'] = $aClassType[$aResult['class'].':'.$aResult['type'].':'.$aResult['admin_level']]['label']; + } elseif (isset($aClassType[$aResult['class'].':'.$aResult['type']]['label']) + && $aClassType[$aResult['class'].':'.$aResult['type']]['label'] + ) { + $aResult['label'] = $aClassType[$aResult['class'].':'.$aResult['type']]['label']; + } + // if tag '&addressdetails=1' is set in query + if ($this->bIncludeAddressDetails) { + // getAddressDetails() is defined in lib.php and uses the SQL function get_addressdata in functions.sql + $aResult['address'] = getAddressDetails($this->oDB, $sLanguagePrefArraySQL, $aResult['place_id'], $aResult['country_code'], $aResultPlaceIDs[$aResult['place_id']]); + if ($aResult['extra_place'] == 'city' && !isset($aResult['address']['city'])) { + $aResult['address'] = array_merge(array('city' => array_shift(array_values($aResult['address']))), $aResult['address']); + } + } + + if ($this->bIncludeExtraTags) { + if ($aResult['extra']) { + $aResult['sExtraTags'] = json_decode($aResult['extra']); + } else { + $aResult['sExtraTags'] = (object) array(); + } + } + + if ($this->bIncludeNameDetails) { + if ($aResult['names']) { + $aResult['sNameDetails'] = json_decode($aResult['names']); + } else { + $aResult['sNameDetails'] = (object) array(); + } + } + + // Adjust importance for the number of exact string matches in the result + $aResult['importance'] = max(0.001, $aResult['importance']); + $iCountWords = 0; + $sAddress = $aResult['langaddress']; + foreach ($aRecheckWords as $i => $sWord) { + if (stripos($sAddress, $sWord)!==false) { + $iCountWords++; + if (preg_match("/(^|,)\s*".preg_quote($sWord, '/')."\s*(,|$)/", $sAddress)) $iCountWords += 0.1; + } + } + + $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 + + $aResult['name'] = $aResult['langaddress']; + // secondary ordering (for results with same importance (the smaller the better): + // - approximate importance of address parts + $aResult['foundorder'] = -$aResult['addressimportance']/10; + // - number of exact matches from the query + if (isset($this->exactMatchCache[$aResult['place_id']])) { + $aResult['foundorder'] -= $this->exactMatchCache[$aResult['place_id']]; + } elseif (isset($this->exactMatchCache[$aResult['parent_place_id']])) { + $aResult['foundorder'] -= $this->exactMatchCache[$aResult['parent_place_id']]; + } + // - importance of the class/type + if (isset($aClassType[$aResult['class'].':'.$aResult['type']]['importance']) + && $aClassType[$aResult['class'].':'.$aResult['type']]['importance'] + ) { + $aResult['foundorder'] += 0.0001 * $aClassType[$aResult['class'].':'.$aResult['type']]['importance']; + } else { + $aResult['foundorder'] += 0.01; + } + if (CONST_Debug) var_dump($aResult); + $aSearchResults[$iResNum] = $aResult; + } + uasort($aSearchResults, 'byImportance'); + + $aOSMIDDone = array(); + $aClassTypeNameDone = array(); + $aToFilter = $aSearchResults; + $aSearchResults = array(); + + $bFirst = true; + foreach ($aToFilter as $iResNum => $aResult) { + $this->aExcludePlaceIDs[$aResult['place_id']] = $aResult['place_id']; + if ($bFirst) { + $fLat = $aResult['lat']; + $fLon = $aResult['lon']; + if (isset($aResult['zoom'])) $iZoom = $aResult['zoom']; + $bFirst = false; + } + if (!$this->bDeDupe || (!isset($aOSMIDDone[$aResult['osm_type'].$aResult['osm_id']]) + && !isset($aClassTypeNameDone[$aResult['osm_type'].$aResult['class'].$aResult['type'].$aResult['name'].$aResult['admin_level']])) + ) { + $aOSMIDDone[$aResult['osm_type'].$aResult['osm_id']] = true; + $aClassTypeNameDone[$aResult['osm_type'].$aResult['class'].$aResult['type'].$aResult['name'].$aResult['admin_level']] = true; + $aSearchResults[] = $aResult; + } + + // Absolute limit on number of results + if (sizeof($aSearchResults) >= $this->iFinalLimit) break; + } + + return $aSearchResults; + } // end lookup() + } // end class diff --cc lib/lib.php index 2278c6c1,092965ba..9de2a548 --- a/lib/lib.php +++ b/lib/lib.php @@@ -1,822 -1,724 +1,724 @@@ $b['importance']?-1:1); - - return ($a['foundorder'] < $b['foundorder']?-1:1); - } - - - function getPreferredLanguages($sLangString=false) - { - if (!$sLangString) - { - // If we have been provided the value in $_GET it overrides browser value - if (isset($_GET['accept-language']) && $_GET['accept-language']) - { - $_SERVER["HTTP_ACCEPT_LANGUAGE"] = $_GET['accept-language']; - $sLangString = $_GET['accept-language']; - } - else if (isset($_SERVER["HTTP_ACCEPT_LANGUAGE"])) - { - $sLangString = $_SERVER["HTTP_ACCEPT_LANGUAGE"]; - } - } - - $aLanguages = array(); - if ($sLangString) - { - if (preg_match_all('/(([a-z]{1,8})(-[a-z]{1,8})?)\s*(;\s*q\s*=\s*(1|0\.[0-9]+))?/i', $sLangString, $aLanguagesParse, PREG_SET_ORDER)) - { - foreach($aLanguagesParse as $iLang => $aLanguage) - { - $aLanguages[$aLanguage[1]] = isset($aLanguage[5])?(float)$aLanguage[5]:1 - ($iLang/100); - if (!isset($aLanguages[$aLanguage[2]])) $aLanguages[$aLanguage[2]] = $aLanguages[$aLanguage[1]]/10; - } - arsort($aLanguages); - } - } - if (!sizeof($aLanguages) && CONST_Default_Language) $aLanguages = array(CONST_Default_Language=>1); - foreach($aLanguages as $sLangauge => $fLangauagePref) - { - $aLangPrefOrder['short_name:'.$sLangauge] = 'short_name:'.$sLangauge; - } - foreach($aLanguages as $sLangauge => $fLangauagePref) - { - $aLangPrefOrder['name:'.$sLangauge] = 'name:'.$sLangauge; - } - foreach($aLanguages as $sLangauge => $fLangauagePref) - { - $aLangPrefOrder['place_name:'.$sLangauge] = 'place_name:'.$sLangauge; - } - foreach($aLanguages as $sLangauge => $fLangauagePref) - { - $aLangPrefOrder['official_name:'.$sLangauge] = 'official_name:'.$sLangauge; - } - $aLangPrefOrder['short_name'] = 'short_name'; - $aLangPrefOrder['name'] = 'name'; - $aLangPrefOrder['place_name'] = 'place_name'; - $aLangPrefOrder['official_name'] = 'official_name'; - $aLangPrefOrder['ref'] = 'ref'; - $aLangPrefOrder['type'] = 'type'; - return $aLangPrefOrder; - } - - - function getWordSets($aWords, $iDepth) - { - $aResult = array(array(join(' ',$aWords))); - $sFirstToken = ''; - if ($iDepth < 7) { - while(sizeof($aWords) > 1) - { - $sWord = array_shift($aWords); - $sFirstToken .= ($sFirstToken?' ':'').$sWord; - $aRest = getWordSets($aWords, $iDepth+1); - foreach($aRest as $aSet) - { - $aResult[] = array_merge(array($sFirstToken),$aSet); - } - } - } - return $aResult; - } - - function getInverseWordSets($aWords, $iDepth) - { - $aResult = array(array(join(' ',$aWords))); - $sFirstToken = ''; - if ($iDepth < 8) - { - while(sizeof($aWords) > 1) - { - $sWord = array_pop($aWords); - $sFirstToken = $sWord.($sFirstToken?' ':'').$sFirstToken; - $aRest = getInverseWordSets($aWords, $iDepth+1); - foreach($aRest as $aSet) - { - $aResult[] = array_merge(array($sFirstToken),$aSet); - } - } - } - return $aResult; - } - - - function getTokensFromSets($aSets) - { - $aTokens = array(); - foreach($aSets as $aSet) - { - foreach($aSet as $sWord) - { - $aTokens[' '.$sWord] = ' '.$sWord; - $aTokens[$sWord] = $sWord; - } - } - return $aTokens; - } - - - /* - GB Postcode functions - */ - - function gbPostcodeCalculate($sPostcode, $sPostcodeSector, $sPostcodeEnd, &$oDB) - { - // Try an exact match on the gb_postcode table - $sSQL = 'select \'AA\', ST_X(ST_Centroid(geometry)) as lon,ST_Y(ST_Centroid(geometry)) as lat from gb_postcode where postcode = \''.$sPostcode.'\''; - $aNearPostcodes = chksql($oDB->getAll($sSQL)); - - if (sizeof($aNearPostcodes)) - { - $aPostcodes = array(); - foreach($aNearPostcodes as $aPostcode) - { - $aPostcodes[] = array('lat' => $aPostcode['lat'], 'lon' => $aPostcode['lon'], 'radius' => 0.005); - } - - return $aPostcodes; - } - - return false; - } - - - function getClassTypes() - { - return array( - 'boundary:administrative:1' => array('label'=>'Continent','frequency'=>0,'icon'=>'poi_boundary_administrative', 'defdiameter' => 0.32,), - 'boundary:administrative:2' => array('label'=>'Country','frequency'=>0,'icon'=>'poi_boundary_administrative', 'defdiameter' => 0.32,), - 'place:country' => array('label'=>'Country','frequency'=>0,'icon'=>'poi_boundary_administrative','defzoom'=>6, 'defdiameter' => 15,), - 'boundary:administrative:3' => array('label'=>'State','frequency'=>0,'icon'=>'poi_boundary_administrative', 'defdiameter' => 0.32,), - 'boundary:administrative:4' => array('label'=>'State','frequency'=>0,'icon'=>'poi_boundary_administrative', 'defdiameter' => 0.32,), - 'place:state' => array('label'=>'State','frequency'=>0,'icon'=>'poi_boundary_administrative','defzoom'=>8, 'defdiameter' => 5.12,), - 'boundary:administrative:5' => array('label'=>'State District','frequency'=>0,'icon'=>'poi_boundary_administrative', 'defdiameter' => 0.32,), - 'boundary:administrative:6' => array('label'=>'County','frequency'=>0,'icon'=>'poi_boundary_administrative', 'defdiameter' => 0.32,), - 'boundary:administrative:7' => array('label'=>'County','frequency'=>0,'icon'=>'poi_boundary_administrative', 'defdiameter' => 0.32,), - 'place:county' => array('label'=>'County','frequency'=>108,'icon'=>'poi_boundary_administrative','defzoom'=>10, 'defdiameter' => 1.28,), - 'boundary:administrative:8' => array('label'=>'City','frequency'=>0,'icon'=>'poi_boundary_administrative', 'defdiameter' => 0.32,), - 'place:city' => array('label'=>'City','frequency'=>66,'icon'=>'poi_place_city','defzoom'=>12, 'defdiameter' => 0.32,), - 'boundary:administrative:9' => array('label'=>'City District','frequency'=>0,'icon'=>'poi_boundary_administrative', 'defdiameter' => 0.32,), - 'boundary:administrative:10' => array('label'=>'Suburb','frequency'=>0,'icon'=>'poi_boundary_administrative', 'defdiameter' => 0.32,), - 'boundary:administrative:11' => array('label'=>'Neighbourhood','frequency'=>0,'icon'=>'poi_boundary_administrative', 'defdiameter' => 0.32,), - 'place:region' => array('label'=>'Region','frequency'=>0,'icon'=>'poi_boundary_administrative','defzoom'=>8, 'defdiameter' => 0.04,), - 'place:island' => array('label'=>'Island','frequency'=>288,'icon'=>'','defzoom'=>11, 'defdiameter' => 0.64,), - 'boundary:administrative' => array('label'=>'Administrative','frequency'=>413,'icon'=>'poi_boundary_administrative', 'defdiameter' => 0.32,), - 'boundary:postal_code' => array('label'=>'Postcode','frequency'=>413,'icon'=>'poi_boundary_administrative', 'defdiameter' => 0.32,), - 'place:town' => array('label'=>'Town','frequency'=>1497,'icon'=>'poi_place_town','defzoom'=>14, 'defdiameter' => 0.08,), - 'place:village' => array('label'=>'Village','frequency'=>11230,'icon'=>'poi_place_village','defzoom'=>15, 'defdiameter' => 0.04,), - 'place:hamlet' => array('label'=>'Hamlet','frequency'=>7075,'icon'=>'poi_place_village','defzoom'=>15, 'defdiameter' => 0.04,), - 'place:suburb' => array('label'=>'Suburb','frequency'=>2528,'icon'=>'poi_place_village', 'defdiameter' => 0.04,), - 'place:locality' => array('label'=>'Locality','frequency'=>4113,'icon'=>'poi_place_village', 'defdiameter' => 0.02,), - 'landuse:farm' => array('label'=>'Farm','frequency'=>1201,'icon'=>'', 'defdiameter' => 0.02,), - 'place:farm' => array('label'=>'Farm','frequency'=>1162,'icon'=>'', 'defdiameter' => 0.02,), - - 'highway:motorway_junction' => array('label'=>'Motorway Junction','frequency'=>1126,'icon'=>'','simplelabel'=>'Junction',), - 'highway:motorway' => array('label'=>'Motorway','frequency'=>4627,'icon'=>'','simplelabel'=>'Road',), - 'highway:trunk' => array('label'=>'Trunk','frequency'=>23084,'icon'=>'','simplelabel'=>'Road',), - 'highway:primary' => array('label'=>'Primary','frequency'=>32138,'icon'=>'','simplelabel'=>'Road',), - 'highway:secondary' => array('label'=>'Secondary','frequency'=>25807,'icon'=>'','simplelabel'=>'Road',), - 'highway:tertiary' => array('label'=>'Tertiary','frequency'=>29829,'icon'=>'','simplelabel'=>'Road',), - 'highway:residential' => array('label'=>'Residential','frequency'=>361498,'icon'=>'','simplelabel'=>'Road',), - 'highway:unclassified' => array('label'=>'Unclassified','frequency'=>66441,'icon'=>'','simplelabel'=>'Road',), - 'highway:living_street' => array('label'=>'Living Street','frequency'=>710,'icon'=>'','simplelabel'=>'Road',), - 'highway:service' => array('label'=>'Service','frequency'=>9963,'icon'=>'','simplelabel'=>'Road',), - 'highway:track' => array('label'=>'Track','frequency'=>2565,'icon'=>'','simplelabel'=>'Road',), - 'highway:road' => array('label'=>'Road','frequency'=>591,'icon'=>'','simplelabel'=>'Road',), - 'highway:byway' => array('label'=>'Byway','frequency'=>346,'icon'=>'','simplelabel'=>'Road',), - 'highway:bridleway' => array('label'=>'Bridleway','frequency'=>1556,'icon'=>'',), - 'highway:cycleway' => array('label'=>'Cycleway','frequency'=>2419,'icon'=>'',), - 'highway:pedestrian' => array('label'=>'Pedestrian','frequency'=>2757,'icon'=>'',), - 'highway:footway' => array('label'=>'Footway','frequency'=>15008,'icon'=>'',), - 'highway:steps' => array('label'=>'Steps','frequency'=>444,'icon'=>'','simplelabel'=>'Footway',), - 'highway:motorway_link' => array('label'=>'Motorway Link','frequency'=>795,'icon'=>'','simplelabel'=>'Road',), - 'highway:trunk_link' => array('label'=>'Trunk Link','frequency'=>1258,'icon'=>'','simplelabel'=>'Road',), - 'highway:primary_link' => array('label'=>'Primary Link','frequency'=>313,'icon'=>'','simplelabel'=>'Road',), - - 'landuse:industrial' => array('label'=>'Industrial','frequency'=>1062,'icon'=>'',), - 'landuse:residential' => array('label'=>'Residential','frequency'=>886,'icon'=>'',), - 'landuse:retail' => array('label'=>'Retail','frequency'=>754,'icon'=>'',), - 'landuse:commercial' => array('label'=>'Commercial','frequency'=>657,'icon'=>'',), - - 'place:airport' => array('label'=>'Airport','frequency'=>36,'icon'=>'transport_airport2', 'defdiameter' => 0.03,), - 'aeroway:aerodrome' => array('label'=>'Aerodrome','frequency'=>36,'icon'=>'transport_airport2', 'defdiameter' => 0.03,), - 'aeroway' => array('label'=>'Aeroway','frequency'=>36,'icon'=>'transport_airport2', 'defdiameter' => 0.03,), - 'railway:station' => array('label'=>'Station','frequency'=>3431,'icon'=>'transport_train_station2', 'defdiameter' => 0.01,), - 'amenity:place_of_worship' => array('label'=>'Place Of Worship','frequency'=>9049,'icon'=>'place_of_worship_unknown3',), - 'amenity:pub' => array('label'=>'Pub','frequency'=>18969,'icon'=>'food_pub',), - 'amenity:bar' => array('label'=>'Bar','frequency'=>164,'icon'=>'food_bar',), - 'amenity:university' => array('label'=>'University','frequency'=>607,'icon'=>'education_university',), - 'tourism:museum' => array('label'=>'Museum','frequency'=>543,'icon'=>'tourist_museum',), - 'amenity:arts_centre' => array('label'=>'Arts Centre','frequency'=>136,'icon'=>'tourist_art_gallery2',), - 'tourism:zoo' => array('label'=>'Zoo','frequency'=>47,'icon'=>'tourist_zoo',), - 'tourism:theme_park' => array('label'=>'Theme Park','frequency'=>24,'icon'=>'poi_point_of_interest',), - 'tourism:attraction' => array('label'=>'Attraction','frequency'=>1463,'icon'=>'poi_point_of_interest',), - 'leisure:golf_course' => array('label'=>'Golf Course','frequency'=>712,'icon'=>'sport_golf',), - 'historic:castle' => array('label'=>'Castle','frequency'=>316,'icon'=>'tourist_castle',), - 'amenity:hospital' => array('label'=>'Hospital','frequency'=>879,'icon'=>'health_hospital',), - 'amenity:school' => array('label'=>'School','frequency'=>8192,'icon'=>'education_school',), - 'amenity:theatre' => array('label'=>'Theatre','frequency'=>371,'icon'=>'tourist_theatre',), - 'amenity:public_building' => array('label'=>'Public Building','frequency'=>985,'icon'=>'',), - 'amenity:library' => array('label'=>'Library','frequency'=>794,'icon'=>'amenity_library',), - 'amenity:townhall' => array('label'=>'Townhall','frequency'=>242,'icon'=>'',), - 'amenity:community_centre' => array('label'=>'Community Centre','frequency'=>157,'icon'=>'',), - 'amenity:fire_station' => array('label'=>'Fire Station','frequency'=>221,'icon'=>'amenity_firestation3',), - 'amenity:police' => array('label'=>'Police','frequency'=>334,'icon'=>'amenity_police2',), - 'amenity:bank' => array('label'=>'Bank','frequency'=>1248,'icon'=>'money_bank2',), - 'amenity:post_office' => array('label'=>'Post Office','frequency'=>859,'icon'=>'amenity_post_office',), - 'leisure:park' => array('label'=>'Park','frequency'=>2378,'icon'=>'',), - 'amenity:park' => array('label'=>'Park','frequency'=>53,'icon'=>'',), - 'landuse:park' => array('label'=>'Park','frequency'=>50,'icon'=>'',), - 'landuse:recreation_ground' => array('label'=>'Recreation Ground','frequency'=>517,'icon'=>'',), - 'tourism:hotel' => array('label'=>'Hotel','frequency'=>2150,'icon'=>'accommodation_hotel2',), - 'tourism:motel' => array('label'=>'Motel','frequency'=>43,'icon'=>'',), - 'amenity:cinema' => array('label'=>'Cinema','frequency'=>277,'icon'=>'tourist_cinema',), - 'tourism:artwork' => array('label'=>'Artwork','frequency'=>171,'icon'=>'tourist_art_gallery2',), - 'historic:archaeological_site' => array('label'=>'Archaeological Site','frequency'=>407,'icon'=>'tourist_archaeological2',), - 'amenity:doctors' => array('label'=>'Doctors','frequency'=>581,'icon'=>'health_doctors',), - 'leisure:sports_centre' => array('label'=>'Sports Centre','frequency'=>767,'icon'=>'sport_leisure_centre',), - 'leisure:swimming_pool' => array('label'=>'Swimming Pool','frequency'=>24,'icon'=>'sport_swimming_outdoor',), - 'shop:supermarket' => array('label'=>'Supermarket','frequency'=>2673,'icon'=>'shopping_supermarket',), - 'shop:convenience' => array('label'=>'Convenience','frequency'=>1469,'icon'=>'shopping_convenience',), - 'amenity:restaurant' => array('label'=>'Restaurant','frequency'=>3179,'icon'=>'food_restaurant',), - 'amenity:fast_food' => array('label'=>'Fast Food','frequency'=>2289,'icon'=>'food_fastfood',), - 'amenity:cafe' => array('label'=>'Cafe','frequency'=>1780,'icon'=>'food_cafe',), - 'tourism:guest_house' => array('label'=>'Guest House','frequency'=>223,'icon'=>'accommodation_bed_and_breakfast',), - 'amenity:pharmacy' => array('label'=>'Pharmacy','frequency'=>733,'icon'=>'health_pharmacy_dispensing',), - 'amenity:fuel' => array('label'=>'Fuel','frequency'=>1308,'icon'=>'transport_fuel',), - 'natural:peak' => array('label'=>'Peak','frequency'=>3212,'icon'=>'poi_peak',), - 'waterway:waterfall' => array('label'=>'Waterfall','frequency'=>24,'icon'=>'',), - 'natural:wood' => array('label'=>'Wood','frequency'=>1845,'icon'=>'landuse_coniferous_and_deciduous',), - 'natural:water' => array('label'=>'Water','frequency'=>1790,'icon'=>'',), - 'landuse:forest' => array('label'=>'Forest','frequency'=>467,'icon'=>'',), - 'landuse:cemetery' => array('label'=>'Cemetery','frequency'=>463,'icon'=>'',), - 'landuse:allotments' => array('label'=>'Allotments','frequency'=>408,'icon'=>'',), - 'landuse:farmyard' => array('label'=>'Farmyard','frequency'=>397,'icon'=>'',), - 'railway:rail' => array('label'=>'Rail','frequency'=>4894,'icon'=>'',), - 'waterway:canal' => array('label'=>'Canal','frequency'=>1723,'icon'=>'',), - 'waterway:river' => array('label'=>'River','frequency'=>4089,'icon'=>'',), - 'waterway:stream' => array('label'=>'Stream','frequency'=>2684,'icon'=>'',), - 'shop:bicycle' => array('label'=>'Bicycle','frequency'=>349,'icon'=>'shopping_bicycle',), - 'shop:clothes' => array('label'=>'Clothes','frequency'=>315,'icon'=>'shopping_clothes',), - 'shop:hairdresser' => array('label'=>'Hairdresser','frequency'=>312,'icon'=>'shopping_hairdresser',), - 'shop:doityourself' => array('label'=>'Doityourself','frequency'=>247,'icon'=>'shopping_diy',), - 'shop:estate_agent' => array('label'=>'Estate Agent','frequency'=>162,'icon'=>'shopping_estateagent2',), - 'shop:car' => array('label'=>'Car','frequency'=>159,'icon'=>'shopping_car',), - 'shop:garden_centre' => array('label'=>'Garden Centre','frequency'=>143,'icon'=>'shopping_garden_centre',), - 'shop:car_repair' => array('label'=>'Car Repair','frequency'=>141,'icon'=>'shopping_car_repair',), - 'shop:newsagent' => array('label'=>'Newsagent','frequency'=>132,'icon'=>'',), - 'shop:bakery' => array('label'=>'Bakery','frequency'=>129,'icon'=>'shopping_bakery',), - 'shop:furniture' => array('label'=>'Furniture','frequency'=>124,'icon'=>'',), - 'shop:butcher' => array('label'=>'Butcher','frequency'=>105,'icon'=>'shopping_butcher',), - 'shop:apparel' => array('label'=>'Apparel','frequency'=>98,'icon'=>'shopping_clothes',), - 'shop:electronics' => array('label'=>'Electronics','frequency'=>96,'icon'=>'',), - 'shop:department_store' => array('label'=>'Department Store','frequency'=>86,'icon'=>'',), - 'shop:books' => array('label'=>'Books','frequency'=>85,'icon'=>'',), - 'shop:yes' => array('label'=>'Shop','frequency'=>68,'icon'=>'',), - 'shop:outdoor' => array('label'=>'Outdoor','frequency'=>67,'icon'=>'',), - 'shop:mall' => array('label'=>'Mall','frequency'=>63,'icon'=>'',), - 'shop:florist' => array('label'=>'Florist','frequency'=>61,'icon'=>'',), - 'shop:charity' => array('label'=>'Charity','frequency'=>60,'icon'=>'',), - 'shop:hardware' => array('label'=>'Hardware','frequency'=>59,'icon'=>'',), - 'shop:laundry' => array('label'=>'Laundry','frequency'=>51,'icon'=>'shopping_laundrette',), - 'shop:shoes' => array('label'=>'Shoes','frequency'=>49,'icon'=>'',), - 'shop:beverages' => array('label'=>'Beverages','frequency'=>48,'icon'=>'shopping_alcohol',), - 'shop:dry_cleaning' => array('label'=>'Dry Cleaning','frequency'=>46,'icon'=>'',), - 'shop:carpet' => array('label'=>'Carpet','frequency'=>45,'icon'=>'',), - 'shop:computer' => array('label'=>'Computer','frequency'=>44,'icon'=>'',), - 'shop:alcohol' => array('label'=>'Alcohol','frequency'=>44,'icon'=>'shopping_alcohol',), - 'shop:optician' => array('label'=>'Optician','frequency'=>55,'icon'=>'health_opticians',), - 'shop:chemist' => array('label'=>'Chemist','frequency'=>42,'icon'=>'health_pharmacy',), - 'shop:gallery' => array('label'=>'Gallery','frequency'=>38,'icon'=>'tourist_art_gallery2',), - 'shop:mobile_phone' => array('label'=>'Mobile Phone','frequency'=>37,'icon'=>'',), - 'shop:sports' => array('label'=>'Sports','frequency'=>37,'icon'=>'',), - 'shop:jewelry' => array('label'=>'Jewelry','frequency'=>32,'icon'=>'shopping_jewelry',), - 'shop:pet' => array('label'=>'Pet','frequency'=>29,'icon'=>'',), - 'shop:beauty' => array('label'=>'Beauty','frequency'=>28,'icon'=>'',), - 'shop:stationery' => array('label'=>'Stationery','frequency'=>25,'icon'=>'',), - 'shop:shopping_centre' => array('label'=>'Shopping Centre','frequency'=>25,'icon'=>'',), - 'shop:general' => array('label'=>'General','frequency'=>25,'icon'=>'',), - 'shop:electrical' => array('label'=>'Electrical','frequency'=>25,'icon'=>'',), - 'shop:toys' => array('label'=>'Toys','frequency'=>23,'icon'=>'',), - 'shop:jeweller' => array('label'=>'Jeweller','frequency'=>23,'icon'=>'',), - 'shop:betting' => array('label'=>'Betting','frequency'=>23,'icon'=>'',), - 'shop:household' => array('label'=>'Household','frequency'=>21,'icon'=>'',), - 'shop:travel_agency' => array('label'=>'Travel Agency','frequency'=>21,'icon'=>'',), - 'shop:hifi' => array('label'=>'Hifi','frequency'=>21,'icon'=>'',), - 'amenity:shop' => array('label'=>'Shop','frequency'=>61,'icon'=>'',), - 'tourism:information' => array('label'=>'Information','frequency'=>224,'icon'=>'amenity_information',), - - 'place:house' => array('label'=>'House','frequency'=>2086,'icon'=>'','defzoom'=>18,), - 'place:house_name' => array('label'=>'House','frequency'=>2086,'icon'=>'','defzoom'=>18,), - 'place:house_number' => array('label'=>'House Number','frequency'=>2086,'icon'=>'','defzoom'=>18,), - 'place:country_code' => array('label'=>'Country Code','frequency'=>2086,'icon'=>'','defzoom'=>18,), - - // - - 'leisure:pitch' => array('label'=>'Pitch','frequency'=>762,'icon'=>'',), - 'highway:unsurfaced' => array('label'=>'Unsurfaced','frequency'=>492,'icon'=>'',), - 'historic:ruins' => array('label'=>'Ruins','frequency'=>483,'icon'=>'tourist_ruin',), - 'amenity:college' => array('label'=>'College','frequency'=>473,'icon'=>'education_school',), - 'historic:monument' => array('label'=>'Monument','frequency'=>470,'icon'=>'tourist_monument',), - 'railway:subway' => array('label'=>'Subway','frequency'=>385,'icon'=>'',), - 'historic:memorial' => array('label'=>'Memorial','frequency'=>382,'icon'=>'tourist_monument',), - 'leisure:nature_reserve' => array('label'=>'Nature Reserve','frequency'=>342,'icon'=>'',), - 'leisure:common' => array('label'=>'Common','frequency'=>322,'icon'=>'',), - 'waterway:lock_gate' => array('label'=>'Lock Gate','frequency'=>321,'icon'=>'',), - 'natural:fell' => array('label'=>'Fell','frequency'=>308,'icon'=>'',), - 'amenity:nightclub' => array('label'=>'Nightclub','frequency'=>292,'icon'=>'',), - 'highway:path' => array('label'=>'Path','frequency'=>287,'icon'=>'',), - 'leisure:garden' => array('label'=>'Garden','frequency'=>285,'icon'=>'',), - 'landuse:reservoir' => array('label'=>'Reservoir','frequency'=>276,'icon'=>'',), - 'leisure:playground' => array('label'=>'Playground','frequency'=>264,'icon'=>'',), - 'leisure:stadium' => array('label'=>'Stadium','frequency'=>212,'icon'=>'',), - 'historic:mine' => array('label'=>'Mine','frequency'=>193,'icon'=>'poi_mine',), - 'natural:cliff' => array('label'=>'Cliff','frequency'=>193,'icon'=>'',), - 'tourism:caravan_site' => array('label'=>'Caravan Site','frequency'=>183,'icon'=>'accommodation_caravan_park',), - 'amenity:bus_station' => array('label'=>'Bus Station','frequency'=>181,'icon'=>'transport_bus_station',), - 'amenity:kindergarten' => array('label'=>'Kindergarten','frequency'=>179,'icon'=>'',), - 'highway:construction' => array('label'=>'Construction','frequency'=>176,'icon'=>'',), - 'amenity:atm' => array('label'=>'Atm','frequency'=>172,'icon'=>'money_atm2',), - 'amenity:emergency_phone' => array('label'=>'Emergency Phone','frequency'=>164,'icon'=>'',), - 'waterway:lock' => array('label'=>'Lock','frequency'=>146,'icon'=>'',), - 'waterway:riverbank' => array('label'=>'Riverbank','frequency'=>143,'icon'=>'',), - 'natural:coastline' => array('label'=>'Coastline','frequency'=>142,'icon'=>'',), - 'tourism:viewpoint' => array('label'=>'Viewpoint','frequency'=>140,'icon'=>'tourist_view_point',), - 'tourism:hostel' => array('label'=>'Hostel','frequency'=>140,'icon'=>'',), - 'tourism:bed_and_breakfast' => array('label'=>'Bed And Breakfast','frequency'=>140,'icon'=>'accommodation_bed_and_breakfast',), - 'railway:halt' => array('label'=>'Halt','frequency'=>135,'icon'=>'',), - 'railway:platform' => array('label'=>'Platform','frequency'=>134,'icon'=>'',), - 'railway:tram' => array('label'=>'Tram','frequency'=>130,'icon'=>'transport_tram_stop',), - 'amenity:courthouse' => array('label'=>'Courthouse','frequency'=>129,'icon'=>'amenity_court',), - 'amenity:recycling' => array('label'=>'Recycling','frequency'=>126,'icon'=>'amenity_recycling',), - 'amenity:dentist' => array('label'=>'Dentist','frequency'=>124,'icon'=>'health_dentist',), - 'natural:beach' => array('label'=>'Beach','frequency'=>121,'icon'=>'tourist_beach',), - 'place:moor' => array('label'=>'Moor','frequency'=>118,'icon'=>'',), - 'amenity:grave_yard' => array('label'=>'Grave Yard','frequency'=>110,'icon'=>'',), - 'waterway:drain' => array('label'=>'Drain','frequency'=>108,'icon'=>'',), - 'landuse:grass' => array('label'=>'Grass','frequency'=>106,'icon'=>'',), - 'landuse:village_green' => array('label'=>'Village Green','frequency'=>106,'icon'=>'',), - 'natural:bay' => array('label'=>'Bay','frequency'=>102,'icon'=>'',), - 'railway:tram_stop' => array('label'=>'Tram Stop','frequency'=>101,'icon'=>'transport_tram_stop',), - 'leisure:marina' => array('label'=>'Marina','frequency'=>98,'icon'=>'',), - 'highway:stile' => array('label'=>'Stile','frequency'=>97,'icon'=>'',), - 'natural:moor' => array('label'=>'Moor','frequency'=>95,'icon'=>'',), - 'railway:light_rail' => array('label'=>'Light Rail','frequency'=>91,'icon'=>'',), - 'railway:narrow_gauge' => array('label'=>'Narrow Gauge','frequency'=>90,'icon'=>'',), - 'natural:land' => array('label'=>'Land','frequency'=>86,'icon'=>'',), - 'amenity:village_hall' => array('label'=>'Village Hall','frequency'=>82,'icon'=>'',), - 'waterway:dock' => array('label'=>'Dock','frequency'=>80,'icon'=>'',), - 'amenity:veterinary' => array('label'=>'Veterinary','frequency'=>79,'icon'=>'',), - 'landuse:brownfield' => array('label'=>'Brownfield','frequency'=>77,'icon'=>'',), - 'leisure:track' => array('label'=>'Track','frequency'=>76,'icon'=>'',), - 'railway:historic_station' => array('label'=>'Historic Station','frequency'=>74,'icon'=>'',), - 'landuse:construction' => array('label'=>'Construction','frequency'=>72,'icon'=>'',), - 'amenity:prison' => array('label'=>'Prison','frequency'=>71,'icon'=>'amenity_prison',), - 'landuse:quarry' => array('label'=>'Quarry','frequency'=>71,'icon'=>'',), - 'amenity:telephone' => array('label'=>'Telephone','frequency'=>70,'icon'=>'',), - 'highway:traffic_signals' => array('label'=>'Traffic Signals','frequency'=>66,'icon'=>'',), - 'natural:heath' => array('label'=>'Heath','frequency'=>62,'icon'=>'',), - 'historic:house' => array('label'=>'House','frequency'=>61,'icon'=>'',), - 'amenity:social_club' => array('label'=>'Social Club','frequency'=>61,'icon'=>'',), - 'landuse:military' => array('label'=>'Military','frequency'=>61,'icon'=>'',), - 'amenity:health_centre' => array('label'=>'Health Centre','frequency'=>59,'icon'=>'',), - 'historic:building' => array('label'=>'Building','frequency'=>58,'icon'=>'',), - 'amenity:clinic' => array('label'=>'Clinic','frequency'=>57,'icon'=>'',), - 'highway:services' => array('label'=>'Services','frequency'=>56,'icon'=>'',), - 'amenity:ferry_terminal' => array('label'=>'Ferry Terminal','frequency'=>55,'icon'=>'',), - 'natural:marsh' => array('label'=>'Marsh','frequency'=>55,'icon'=>'',), - 'natural:hill' => array('label'=>'Hill','frequency'=>54,'icon'=>'',), - 'highway:raceway' => array('label'=>'Raceway','frequency'=>53,'icon'=>'',), - 'amenity:taxi' => array('label'=>'Taxi','frequency'=>47,'icon'=>'',), - 'amenity:take_away' => array('label'=>'Take Away','frequency'=>45,'icon'=>'',), - 'amenity:car_rental' => array('label'=>'Car Rental','frequency'=>44,'icon'=>'',), - 'place:islet' => array('label'=>'Islet','frequency'=>44,'icon'=>'',), - 'amenity:nursery' => array('label'=>'Nursery','frequency'=>44,'icon'=>'',), - 'amenity:nursing_home' => array('label'=>'Nursing Home','frequency'=>43,'icon'=>'',), - 'amenity:toilets' => array('label'=>'Toilets','frequency'=>38,'icon'=>'',), - 'amenity:hall' => array('label'=>'Hall','frequency'=>38,'icon'=>'',), - 'waterway:boatyard' => array('label'=>'Boatyard','frequency'=>36,'icon'=>'',), - 'highway:mini_roundabout' => array('label'=>'Mini Roundabout','frequency'=>35,'icon'=>'',), - 'historic:manor' => array('label'=>'Manor','frequency'=>35,'icon'=>'',), - 'tourism:chalet' => array('label'=>'Chalet','frequency'=>34,'icon'=>'',), - 'amenity:bicycle_parking' => array('label'=>'Bicycle Parking','frequency'=>34,'icon'=>'',), - 'amenity:hotel' => array('label'=>'Hotel','frequency'=>34,'icon'=>'',), - 'waterway:weir' => array('label'=>'Weir','frequency'=>33,'icon'=>'',), - 'natural:wetland' => array('label'=>'Wetland','frequency'=>33,'icon'=>'',), - 'natural:cave_entrance' => array('label'=>'Cave Entrance','frequency'=>32,'icon'=>'',), - 'amenity:crematorium' => array('label'=>'Crematorium','frequency'=>31,'icon'=>'',), - 'tourism:picnic_site' => array('label'=>'Picnic Site','frequency'=>31,'icon'=>'',), - 'landuse:wood' => array('label'=>'Wood','frequency'=>30,'icon'=>'',), - 'landuse:basin' => array('label'=>'Basin','frequency'=>30,'icon'=>'',), - 'natural:tree' => array('label'=>'Tree','frequency'=>30,'icon'=>'',), - 'leisure:slipway' => array('label'=>'Slipway','frequency'=>29,'icon'=>'',), - 'landuse:meadow' => array('label'=>'Meadow','frequency'=>29,'icon'=>'',), - 'landuse:piste' => array('label'=>'Piste','frequency'=>28,'icon'=>'',), - 'amenity:care_home' => array('label'=>'Care Home','frequency'=>28,'icon'=>'',), - 'amenity:club' => array('label'=>'Club','frequency'=>28,'icon'=>'',), - 'amenity:medical_centre' => array('label'=>'Medical Centre','frequency'=>27,'icon'=>'',), - 'historic:roman_road' => array('label'=>'Roman Road','frequency'=>27,'icon'=>'',), - 'historic:fort' => array('label'=>'Fort','frequency'=>26,'icon'=>'',), - 'railway:subway_entrance' => array('label'=>'Subway Entrance','frequency'=>26,'icon'=>'',), - 'historic:yes' => array('label'=>'Historic','frequency'=>25,'icon'=>'',), - 'highway:gate' => array('label'=>'Gate','frequency'=>25,'icon'=>'',), - 'leisure:fishing' => array('label'=>'Fishing','frequency'=>24,'icon'=>'',), - 'historic:museum' => array('label'=>'Museum','frequency'=>24,'icon'=>'',), - 'amenity:car_wash' => array('label'=>'Car Wash','frequency'=>24,'icon'=>'',), - 'railway:level_crossing' => array('label'=>'Level Crossing','frequency'=>23,'icon'=>'',), - 'leisure:bird_hide' => array('label'=>'Bird Hide','frequency'=>23,'icon'=>'',), - 'natural:headland' => array('label'=>'Headland','frequency'=>21,'icon'=>'',), - 'tourism:apartments' => array('label'=>'Apartments','frequency'=>21,'icon'=>'',), - 'amenity:shopping' => array('label'=>'Shopping','frequency'=>21,'icon'=>'',), - 'natural:scrub' => array('label'=>'Scrub','frequency'=>20,'icon'=>'',), - 'natural:fen' => array('label'=>'Fen','frequency'=>20,'icon'=>'',), - 'building:yes' => array('label'=>'Building','frequency'=>200,'icon'=>'',), - 'mountain_pass:yes' => array('label'=>'Mountain Pass','frequency'=>200,'icon'=>'',), - - 'amenity:parking' => array('label'=>'Parking','frequency'=>3157,'icon'=>'',), - 'highway:bus_stop' => array('label'=>'Bus Stop','frequency'=>35777,'icon'=>'transport_bus_stop2',), - 'place:postcode' => array('label'=>'Postcode','frequency'=>27267,'icon'=>'',), - 'amenity:post_box' => array('label'=>'Post Box','frequency'=>9613,'icon'=>'',), - - 'place:houses' => array('label'=>'Houses','frequency'=>85,'icon'=>'',), - 'railway:preserved' => array('label'=>'Preserved','frequency'=>227,'icon'=>'',), - 'waterway:derelict_canal' => array('label'=>'Derelict Canal','frequency'=>21,'icon'=>'',), - 'amenity:dead_pub' => array('label'=>'Dead Pub','frequency'=>20,'icon'=>'',), - 'railway:disused_station' => array('label'=>'Disused Station','frequency'=>114,'icon'=>'',), - 'railway:abandoned' => array('label'=>'Abandoned','frequency'=>641,'icon'=>'',), - 'railway:disused' => array('label'=>'Disused','frequency'=>72,'icon'=>'',), - ); - } - - - function getClassTypesWithImportance() - { - $aOrders = getClassTypes(); - $i = 1; - foreach($aOrders as $sID => $a) - { - $aOrders[$sID]['importance'] = $i++; - } - return $aOrders; - } - - function getResultDiameter($aResult) - { - $aClassType = getClassTypes(); - - $fDiameter = 0.0001; - - if (isset($aResult['class']) - && isset($aResult['type']) - && isset($aResult['admin_level']) - && isset($aClassType[$aResult['class'].':'.$aResult['type'].':'.$aResult['admin_level']]['defdiameter']) - && $aClassType[$aResult['class'].':'.$aResult['type'].':'.$aResult['admin_level']]['defdiameter']) - { - $fDiameter = $aClassType[$aResult['class'].':'.$aResult['type'].':'.$aResult['admin_level']]['defdiameter']; - } - elseif (isset($aResult['class']) - && isset($aResult['type']) - && isset($aClassType[$aResult['class'].':'.$aResult['type']]['defdiameter']) - && $aClassType[$aResult['class'].':'.$aResult['type']]['defdiameter']) - { - $fDiameter = $aClassType[$aResult['class'].':'.$aResult['type']]['defdiameter']; - } - - return $fDiameter; - } - - - function javascript_renderData($xVal, $iOptions = 0) - { - if (defined('PHP_VERSION_ID') && PHP_VERSION_ID > 50400) - $iOptions |= JSON_UNESCAPED_UNICODE; - $jsonout = json_encode($xVal, $iOptions); - - if( ! isset($_GET['json_callback'])) - { - header("Content-Type: application/json; charset=UTF-8"); - echo $jsonout; - } else - { - if (preg_match('/^[$_\p{L}][$_\p{L}\p{Nd}.[\]]*$/u',$_GET['json_callback'])) - { - header("Content-Type: application/javascript; charset=UTF-8"); - echo $_GET['json_callback'].'('.$jsonout.')'; - } - else - { - header('HTTP/1.0 400 Bad Request'); - } - } - } - - - function _debugDumpGroupedSearches($aData, $aTokens) - { - $aWordsIDs = array(); - if ($aTokens) - { - foreach($aTokens as $sToken => $aWords) - { - if ($aWords) - { - foreach($aWords as $aToken) - { - $aWordsIDs[$aToken['word_id']] = $sToken.'('.$aToken['word_id'].')'; - } - } - } - } - echo ""; - echo ""; - foreach($aData as $iRank => $aRankedSet) - { - foreach($aRankedSet as $aRow) - { - echo ""; - echo ""; - - echo ""; - - echo ""; - - echo ""; - - echo ""; - - echo ""; - - echo ""; - echo ""; - echo ""; - - echo ""; - - echo ""; - echo ""; - echo ""; - - echo ""; - } - } - echo "
rankName TokensName NotAddress TokensAddress Notcountryoperatorclasstypehouse#LatLonRadius
$iRank"; - $sSep = ''; - foreach($aRow['aName'] as $iWordID) - { - echo $sSep.'#'.$aWordsIDs[$iWordID].'#'; - $sSep = ', '; - } - echo ""; - $sSep = ''; - foreach($aRow['aNameNonSearch'] as $iWordID) - { - echo $sSep.'#'.$aWordsIDs[$iWordID].'#'; - $sSep = ', '; - } - echo ""; - $sSep = ''; - foreach($aRow['aAddress'] as $iWordID) - { - echo $sSep.'#'.$aWordsIDs[$iWordID].'#'; - $sSep = ', '; - } - echo ""; - $sSep = ''; - foreach($aRow['aAddressNonSearch'] as $iWordID) - { - echo $sSep.'#'.$aWordsIDs[$iWordID].'#'; - $sSep = ', '; - } - echo "".$aRow['sCountryCode']."".$aRow['sOperator']."".$aRow['sClass']."".$aRow['sType']."".$aRow['sHouseNumber']."".$aRow['fLat']."".$aRow['fLon']."".$aRow['fRadius']."
"; - } - - - function getAddressDetails(&$oDB, $sLanguagePrefArraySQL, $iPlaceID, $sCountryCode = false, $housenumber = -1, $bRaw = false) - { - $sSQL = "select *,get_name_by_language(name,$sLanguagePrefArraySQL) as localname from get_addressdata($iPlaceID, $housenumber)"; - if (!$bRaw) $sSQL .= " WHERE isaddress OR type = 'country_code'"; - $sSQL .= " order by rank_address desc,isaddress desc"; - - $aAddressLines = chksql($oDB->getAll($sSQL)); - if ($bRaw) return $aAddressLines; - //echo "
";
- 		//var_dump($aAddressLines);
- 		$aAddress = array();
- 		$aFallback = array();
- 		$aClassType = getClassTypes();
- 		foreach($aAddressLines as $aLine)
- 		{
- 			$bFallback = false;
- 			$aTypeLabel = false;
- 			if (isset($aClassType[$aLine['class'].':'.$aLine['type'].':'.$aLine['admin_level']])) $aTypeLabel = $aClassType[$aLine['class'].':'.$aLine['type'].':'.$aLine['admin_level']];
- 			elseif (isset($aClassType[$aLine['class'].':'.$aLine['type']])) $aTypeLabel = $aClassType[$aLine['class'].':'.$aLine['type']];
- 			elseif (isset($aClassType['boundary:administrative:'.((int)($aLine['rank_address']/2))]))
- 			{
- 				$aTypeLabel = $aClassType['boundary:administrative:'.((int)($aLine['rank_address']/2))];
- 				$bFallback = true;
- 			}
- 			else
- 			{
- 				$aTypeLabel = array('simplelabel'=>'address'.$aLine['rank_address']);
- 				$bFallback = true;
- 			}
- 			if ($aTypeLabel && ((isset($aLine['localname']) && $aLine['localname']) || (isset($aLine['housenumber']) && $aLine['housenumber'])))
- 			{
- 				$sTypeLabel = strtolower(isset($aTypeLabel['simplelabel'])?$aTypeLabel['simplelabel']:$aTypeLabel['label']);
- 				$sTypeLabel = str_replace(' ','_',$sTypeLabel);
- 				if (!isset($aAddress[$sTypeLabel]) || (isset($aFallback[$sTypeLabel]) && $aFallback[$sTypeLabel]) || $aLine['class'] == 'place')
- 				{
- 					$aAddress[$sTypeLabel] = $aLine['localname']?$aLine['localname']:$aLine['housenumber'];
- 				}
- 				$aFallback[$sTypeLabel] = $bFallback;
- 			}
- 		}
- 		return $aAddress;
- 	}
- 
- 
- 	function addQuotes($s)
- 	{
- 		return "'".$s."'";
- 	}
- 
- 	// returns boolean
- 	function validLatLon($fLat,$fLon)
- 	{
- 		return ($fLat <= 90.1 && $fLat >= -90.1 && $fLon <= 180.1 && $fLon >= -180.1);
- 	}
- 
- 	// Do we have anything that looks like a lat/lon pair?
- 	// returns array(lat,lon,query_with_lat_lon_removed)
- 	// or null
- 	function looksLikeLatLonPair($sQuery)
- 	{
- 		$sFound    = null;
- 		$fQueryLat = null;
- 		$fQueryLon = null;
- 
- 		// degrees decimal minutes
- 		// N 40 26.767, W 79 58.933
- 		// N 40°26.767′, W 79°58.933′
- 		//                  1         2                   3                  4         5            6
- 		if (preg_match('/\\b([NS])[ ]+([0-9]+[0-9.]*)[° ]+([0-9.]+)?[′\']*[, ]+([EW])[ ]+([0-9]+)[° ]+([0-9]+[0-9.]*)[′\']*?\\b/', $sQuery, $aData))
- 		{
- 			$sFound    = $aData[0];
- 			$fQueryLat = ($aData[1]=='N'?1:-1) * ($aData[2] + $aData[3]/60);
- 			$fQueryLon = ($aData[4]=='E'?1:-1) * ($aData[5] + $aData[6]/60);
- 		}
- 		// degrees decimal minutes
- 		// 40 26.767 N, 79 58.933 W
- 		// 40° 26.767′ N 79° 58.933′ W
- 		//                      1             2                      3          4            5                    6
- 		elseif (preg_match('/\\b([0-9]+)[° ]+([0-9]+[0-9.]*)?[′\']*[ ]+([NS])[, ]+([0-9]+)[° ]+([0-9]+[0-9.]*)?[′\' ]+([EW])\\b/', $sQuery, $aData))
- 		{
- 			$sFound    = $aData[0];
- 			$fQueryLat = ($aData[3]=='N'?1:-1) * ($aData[1] + $aData[2]/60);
- 			$fQueryLon = ($aData[6]=='E'?1:-1) * ($aData[4] + $aData[5]/60);
- 		}
- 		// degrees decimal seconds
- 		// N 40 26 46 W 79 58 56
- 		// N 40° 26′ 46″, W 79° 58′ 56″
- 		//                      1        2            3            4                5        6            7            8
- 		elseif (preg_match('/\\b([NS])[ ]([0-9]+)[° ]+([0-9]+)[′\' ]+([0-9]+)[″"]*[, ]+([EW])[ ]([0-9]+)[° ]+([0-9]+)[′\' ]+([0-9]+)[″"]*\\b/', $sQuery, $aData))
- 		{
- 			$sFound    = $aData[0];
- 			$fQueryLat = ($aData[1]=='N'?1:-1) * ($aData[2] + $aData[3]/60 + $aData[4]/3600);
- 			$fQueryLon = ($aData[5]=='E'?1:-1) * ($aData[6] + $aData[7]/60 + $aData[8]/3600);
- 		}
- 		// degrees decimal seconds
- 		// 40 26 46 N 79 58 56 W
- 		// 40° 26′ 46″ N, 79° 58′ 56″ W
- 		//                      1            2            3            4          5            6            7            8
- 		elseif (preg_match('/\\b([0-9]+)[° ]+([0-9]+)[′\' ]+([0-9]+)[″" ]+([NS])[, ]+([0-9]+)[° ]+([0-9]+)[′\' ]+([0-9]+)[″" ]+([EW])\\b/', $sQuery, $aData))
- 		{
- 			$sFound    = $aData[0];
- 			$fQueryLat = ($aData[4]=='N'?1:-1) * ($aData[1] + $aData[2]/60 + $aData[3]/3600);
- 			$fQueryLon = ($aData[8]=='E'?1:-1) * ($aData[5] + $aData[6]/60 + $aData[7]/3600);
- 		}
- 		// degrees decimal
- 		// N 40.446° W 79.982°
- 		//                      1        2                               3        4
- 		elseif (preg_match('/\\b([NS])[ ]([0-9]+[0-9]*\\.[0-9]+)[°]*[, ]+([EW])[ ]([0-9]+[0-9]*\\.[0-9]+)[°]*\\b/', $sQuery, $aData))
- 		{
- 			$sFound    = $aData[0];
- 			$fQueryLat = ($aData[1]=='N'?1:-1) * ($aData[2]);
- 			$fQueryLon = ($aData[3]=='E'?1:-1) * ($aData[4]);
- 		}
- 		// degrees decimal
- 		// 40.446° N 79.982° W
- 		//                      1                           2          3                           4
- 		elseif (preg_match('/\\b([0-9]+[0-9]*\\.[0-9]+)[° ]+([NS])[, ]+([0-9]+[0-9]*\\.[0-9]+)[° ]+([EW])\\b/', $sQuery, $aData))
- 		{
- 			$sFound    = $aData[0];
- 			$fQueryLat = ($aData[2]=='N'?1:-1) * ($aData[1]);
- 			$fQueryLon = ($aData[4]=='E'?1:-1) * ($aData[3]);
- 		}
- 		// degrees decimal
- 		// 12.34, 56.78
- 		// [12.456,-78.90]
- 		//                   1          2                             3                        4
- 		elseif (preg_match('/(\\[|^|\\b)(-?[0-9]+[0-9]*\\.[0-9]+)[, ]+(-?[0-9]+[0-9]*\\.[0-9]+)(\\]|$|\\b)/', $sQuery, $aData))
- 		{
- 			$sFound    = $aData[0];
- 			$fQueryLat = $aData[2];
- 			$fQueryLon = $aData[3];
- 		}
- 
- 		if (!validLatLon($fQueryLat, $fQueryLon)) return;
- 		$sQuery = trim(str_replace($sFound, ' ', $sQuery));
- 
- 		return array('lat' => $fQueryLat, 'lon' => $fQueryLon, 'query' => $sQuery);
- 	}
- 
- 
- 	function geometryText2Points($geometry_as_text, $fRadius)
- 	{
- 		$aPolyPoints = NULL;
- 		if (preg_match('#POLYGON\\(\\(([- 0-9.,]+)#', $geometry_as_text, $aMatch))
- 		{
- 			preg_match_all('/(-?[0-9.]+) (-?[0-9.]+)/', $aMatch[1], $aPolyPoints, PREG_SET_ORDER);
- 		}
- 		elseif (preg_match('#LINESTRING\\(([- 0-9.,]+)#', $geometry_as_text, $aMatch))
- 		{
- 			preg_match_all('/(-?[0-9.]+) (-?[0-9.]+)/', $aMatch[1], $aPolyPoints, PREG_SET_ORDER);
- 		}
- /*		elseif (preg_match('#MULTIPOLYGON\\(\\(\\(([- 0-9.,]+)#', $geometry_as_text, $aMatch))
- 		{
- 			preg_match_all('/(-?[0-9.]+) (-?[0-9.]+)/', $aMatch[1], $aPolyPoints, PREG_SET_ORDER);
-         }*/
- 		elseif (preg_match('#POINT\\((-?[0-9.]+) (-?[0-9.]+)\\)#', $geometry_as_text, $aMatch))
- 		{
- 			$aPolyPoints = createPointsAroundCenter($aMatch[1], $aMatch[2], $fRadius);
- 		}
- 
- 		if (isset($aPolyPoints))
- 		{
- 			$aResultPoints = array();
- 			foreach($aPolyPoints as $aPoint)
- 			{
- 				$aResultPoints[] = array($aPoint[1], $aPoint[2]);
- 			}
- 			return $aResultPoints;
- 		}
- 
- 		return;
- 	}
- 
- 	function createPointsAroundCenter($fLon, $fLat, $fRadius)
- 	{
- 			$iSteps = max(8, min(100, ($fRadius * 40000)^2));
- 			$fStepSize = (2*pi())/$iSteps;
- 			$aPolyPoints = array();
- 			for($f = 0; $f < 2*pi(); $f += $fStepSize)
- 			{
- 				$aPolyPoints[] = array('', $fLon+($fRadius*sin($f)), $fLat+($fRadius*cos($f)) );
- 			}
- 			return $aPolyPoints;
- 	}
+ 
+ function fail($sError, $sUserError = false)
+ {
+     if (!$sUserError) $sUserError = $sError;
+     error_log('ERROR: '.$sError);
+     echo $sUserError."\n";
+     exit(-1);
+ }
+ 
+ 
+ function getProcessorCount()
+ {
+     $sCPU = file_get_contents('/proc/cpuinfo');
+     preg_match_all('#processor\s+: [0-9]+#', $sCPU, $aMatches);
+     return sizeof($aMatches[0]);
+ }
+ 
+ 
+ function getTotalMemoryMB()
+ {
+     $sCPU = file_get_contents('/proc/meminfo');
+     preg_match('#MemTotal: +([0-9]+) kB#', $sCPU, $aMatches);
+     return (int)($aMatches[1]/1024);
+ }
+ 
+ 
+ function getCacheMemoryMB()
+ {
+     $sCPU = file_get_contents('/proc/meminfo');
+     preg_match('#Cached: +([0-9]+) kB#', $sCPU, $aMatches);
+     return (int)($aMatches[1]/1024);
+ }
+ 
+ 
+ function bySearchRank($a, $b)
+ {
+     if ($a['iSearchRank'] == $b['iSearchRank'])
+         return strlen($a['sOperator']) + strlen($a['sHouseNumber']) - strlen($b['sOperator']) - strlen($b['sHouseNumber']);
+     return ($a['iSearchRank'] < $b['iSearchRank']?-1:1);
+ }
+ 
+ 
+ function byImportance($a, $b)
+ {
+     if ($a['importance'] != $b['importance'])
+         return ($a['importance'] > $b['importance']?-1:1);
+ 
+     return ($a['foundorder'] < $b['foundorder']?-1:1);
+ }
+ 
+ 
+ function getWordSets($aWords, $iDepth)
+ {
+     $aResult = array(array(join(' ', $aWords)));
+     $sFirstToken = '';
+     if ($iDepth < 8) {
+         while (sizeof($aWords) > 1) {
+             $sWord = array_shift($aWords);
+             $sFirstToken .= ($sFirstToken?' ':'').$sWord;
+             $aRest = getWordSets($aWords, $iDepth+1);
+             foreach ($aRest as $aSet) {
+                 $aResult[] = array_merge(array($sFirstToken), $aSet);
+             }
+         }
+     }
+     return $aResult;
+ }
+ 
+ function getInverseWordSets($aWords, $iDepth)
+ {
+     $aResult = array(array(join(' ', $aWords)));
+     $sFirstToken = '';
+     if ($iDepth < 8) {
+         while (sizeof($aWords) > 1) {
+             $sWord = array_pop($aWords);
+             $sFirstToken = $sWord.($sFirstToken?' ':'').$sFirstToken;
+             $aRest = getInverseWordSets($aWords, $iDepth+1);
+             foreach ($aRest as $aSet) {
+                 $aResult[] = array_merge(array($sFirstToken), $aSet);
+             }
+         }
+     }
+     return $aResult;
+ }
+ 
+ 
+ function getTokensFromSets($aSets)
+ {
+     $aTokens = array();
+     foreach ($aSets as $aSet) {
+         foreach ($aSet as $sWord) {
+             $aTokens[' '.$sWord] = ' '.$sWord;
+             $aTokens[$sWord] = $sWord;
+         }
+     }
+     return $aTokens;
+ }
+ 
+ 
+ function gbPostcodeCalculate($sPostcode, $sPostcodeSector, $sPostcodeEnd, &$oDB)
+ {
+     // Try an exact match on the gb_postcode table
+     $sSQL = 'select \'AA\', ST_X(ST_Centroid(geometry)) as lon,ST_Y(ST_Centroid(geometry)) as lat from gb_postcode where postcode = \''.$sPostcode.'\'';
+     $aNearPostcodes = chksql($oDB->getAll($sSQL));
+ 
+     if (sizeof($aNearPostcodes)) {
+         $aPostcodes = array();
+         foreach ($aNearPostcodes as $aPostcode) {
+             $aPostcodes[] = array('lat' => $aPostcode['lat'], 'lon' => $aPostcode['lon'], 'radius' => 0.005);
+         }
+ 
+         return $aPostcodes;
+     }
+ 
+     return false;
+ }
+ 
+ 
+ function getClassTypes()
+ {
+     return array(
+             'boundary:administrative:1' => array('label' => 'Continent', 'frequency' => 0, 'icon' => 'poi_boundary_administrative', 'defdiameter' => 0.32),
+             'boundary:administrative:2' => array('label' => 'Country', 'frequency' => 0, 'icon' => 'poi_boundary_administrative', 'defdiameter' => 0.32),
+             'place:country' => array('label' => 'Country', 'frequency' => 0, 'icon' => 'poi_boundary_administrative', 'defzoom' => 6, 'defdiameter' => 15),
+             'boundary:administrative:3' => array('label' => 'State', 'frequency' => 0, 'icon' => 'poi_boundary_administrative', 'defdiameter' => 0.32),
+             'boundary:administrative:4' => array('label' => 'State', 'frequency' => 0, 'icon' => 'poi_boundary_administrative', 'defdiameter' => 0.32),
+             'place:state' => array('label' => 'State', 'frequency' => 0, 'icon' => 'poi_boundary_administrative', 'defzoom' => 8, 'defdiameter' => 5.12),
+             'boundary:administrative:5' => array('label' => 'State District', 'frequency' => 0, 'icon' => 'poi_boundary_administrative', 'defdiameter' => 0.32),
+             'boundary:administrative:6' => array('label' => 'County', 'frequency' => 0, 'icon' => 'poi_boundary_administrative', 'defdiameter' => 0.32),
+             'boundary:administrative:7' => array('label' => 'County', 'frequency' => 0, 'icon' => 'poi_boundary_administrative', 'defdiameter' => 0.32),
+             'place:county' => array('label' => 'County', 'frequency' => 108, 'icon' => 'poi_boundary_administrative', 'defzoom' => 10, 'defdiameter' => 1.28),
+             'boundary:administrative:8' => array('label' => 'City', 'frequency' => 0, 'icon' => 'poi_boundary_administrative', 'defdiameter' => 0.32),
+             'place:city' => array('label' => 'City', 'frequency' => 66, 'icon' => 'poi_place_city', 'defzoom' => 12, 'defdiameter' => 0.32),
+             'boundary:administrative:9' => array('label' => 'City District', 'frequency' => 0, 'icon' => 'poi_boundary_administrative', 'defdiameter' => 0.32),
+             'boundary:administrative:10' => array('label' => 'Suburb', 'frequency' => 0, 'icon' => 'poi_boundary_administrative', 'defdiameter' => 0.32),
+             'boundary:administrative:11' => array('label' => 'Neighbourhood', 'frequency' => 0, 'icon' => 'poi_boundary_administrative', 'defdiameter' => 0.32),
+             'place:region' => array('label' => 'Region', 'frequency' => 0, 'icon' => 'poi_boundary_administrative', 'defzoom' => 8, 'defdiameter' => 0.04),
+             'place:island' => array('label' => 'Island', 'frequency' => 288, 'icon' => '', 'defzoom' => 11, 'defdiameter' => 0.64),
+             'boundary:administrative' => array('label' => 'Administrative', 'frequency' => 413, 'icon' => 'poi_boundary_administrative', 'defdiameter' => 0.32),
+             'boundary:postal_code' => array('label' => 'Postcode', 'frequency' => 413, 'icon' => 'poi_boundary_administrative', 'defdiameter' => 0.32),
+             'place:town' => array('label' => 'Town', 'frequency' => 1497, 'icon' => 'poi_place_town', 'defzoom' => 14, 'defdiameter' => 0.08),
+             'place:village' => array('label' => 'Village', 'frequency' => 11230, 'icon' => 'poi_place_village', 'defzoom' => 15, 'defdiameter' => 0.04),
+             'place:hamlet' => array('label' => 'Hamlet', 'frequency' => 7075, 'icon' => 'poi_place_village', 'defzoom' => 15, 'defdiameter' => 0.04),
+             'place:suburb' => array('label' => 'Suburb', 'frequency' => 2528, 'icon' => 'poi_place_village', 'defdiameter' => 0.04),
+             'place:locality' => array('label' => 'Locality', 'frequency' => 4113, 'icon' => 'poi_place_village', 'defdiameter' => 0.02),
+             'landuse:farm' => array('label' => 'Farm', 'frequency' => 1201, 'icon' => '', 'defdiameter' => 0.02),
+             'place:farm' => array('label' => 'Farm', 'frequency' => 1162, 'icon' => '', 'defdiameter' => 0.02),
+ 
+             'highway:motorway_junction' => array('label' => 'Motorway Junction', 'frequency' => 1126, 'icon' => '', 'simplelabel' => 'Junction'),
+             'highway:motorway' => array('label' => 'Motorway', 'frequency' => 4627, 'icon' => '', 'simplelabel' => 'Road'),
+             'highway:trunk' => array('label' => 'Trunk', 'frequency' => 23084, 'icon' => '', 'simplelabel' => 'Road'),
+             'highway:primary' => array('label' => 'Primary', 'frequency' => 32138, 'icon' => '', 'simplelabel' => 'Road'),
+             'highway:secondary' => array('label' => 'Secondary', 'frequency' => 25807, 'icon' => '', 'simplelabel' => 'Road'),
+             'highway:tertiary' => array('label' => 'Tertiary', 'frequency' => 29829, 'icon' => '', 'simplelabel' => 'Road'),
+             'highway:residential' => array('label' => 'Residential', 'frequency' => 361498, 'icon' => '', 'simplelabel' => 'Road'),
+             'highway:unclassified' => array('label' => 'Unclassified', 'frequency' => 66441, 'icon' => '', 'simplelabel' => 'Road'),
+             'highway:living_street' => array('label' => 'Living Street', 'frequency' => 710, 'icon' => '', 'simplelabel' => 'Road'),
+             'highway:service' => array('label' => 'Service', 'frequency' => 9963, 'icon' => '', 'simplelabel' => 'Road'),
+             'highway:track' => array('label' => 'Track', 'frequency' => 2565, 'icon' => '', 'simplelabel' => 'Road'),
+             'highway:road' => array('label' => 'Road', 'frequency' => 591, 'icon' => '', 'simplelabel' => 'Road'),
+             'highway:byway' => array('label' => 'Byway', 'frequency' => 346, 'icon' => '', 'simplelabel' => 'Road'),
+             'highway:bridleway' => array('label' => 'Bridleway', 'frequency' => 1556, 'icon' => ''),
+             'highway:cycleway' => array('label' => 'Cycleway', 'frequency' => 2419, 'icon' => ''),
+             'highway:pedestrian' => array('label' => 'Pedestrian', 'frequency' => 2757, 'icon' => ''),
+             'highway:footway' => array('label' => 'Footway', 'frequency' => 15008, 'icon' => ''),
+             'highway:steps' => array('label' => 'Steps', 'frequency' => 444, 'icon' => '', 'simplelabel' => 'Footway'),
+             'highway:motorway_link' => array('label' => 'Motorway Link', 'frequency' => 795, 'icon' => '', 'simplelabel' => 'Road'),
+             'highway:trunk_link' => array('label' => 'Trunk Link', 'frequency' => 1258, 'icon' => '', 'simplelabel' => 'Road'),
+             'highway:primary_link' => array('label' => 'Primary Link', 'frequency' => 313, 'icon' => '', 'simplelabel' => 'Road'),
+ 
+             'landuse:industrial' => array('label' => 'Industrial', 'frequency' => 1062, 'icon' => ''),
+             'landuse:residential' => array('label' => 'Residential', 'frequency' => 886, 'icon' => ''),
+             'landuse:retail' => array('label' => 'Retail', 'frequency' => 754, 'icon' => ''),
+             'landuse:commercial' => array('label' => 'Commercial', 'frequency' => 657, 'icon' => ''),
+ 
+             'place:airport' => array('label' => 'Airport', 'frequency' => 36, 'icon' => 'transport_airport2', 'defdiameter' => 0.03),
+             'aeroway:aerodrome' => array('label' => 'Aerodrome', 'frequency' => 36, 'icon' => 'transport_airport2', 'defdiameter' => 0.03),
+             'aeroway' => array('label' => 'Aeroway', 'frequency' => 36, 'icon' => 'transport_airport2', 'defdiameter' => 0.03),
+             'railway:station' => array('label' => 'Station', 'frequency' => 3431, 'icon' => 'transport_train_station2', 'defdiameter' => 0.01),
+             'amenity:place_of_worship' => array('label' => 'Place Of Worship', 'frequency' => 9049, 'icon' => 'place_of_worship_unknown3'),
+             'amenity:pub' => array('label' => 'Pub', 'frequency' => 18969, 'icon' => 'food_pub'),
+             'amenity:bar' => array('label' => 'Bar', 'frequency' => 164, 'icon' => 'food_bar'),
+             'amenity:university' => array('label' => 'University', 'frequency' => 607, 'icon' => 'education_university'),
+             'tourism:museum' => array('label' => 'Museum', 'frequency' => 543, 'icon' => 'tourist_museum'),
+             'amenity:arts_centre' => array('label' => 'Arts Centre', 'frequency' => 136, 'icon' => 'tourist_art_gallery2'),
+             'tourism:zoo' => array('label' => 'Zoo', 'frequency' => 47, 'icon' => 'tourist_zoo'),
+             'tourism:theme_park' => array('label' => 'Theme Park', 'frequency' => 24, 'icon' => 'poi_point_of_interest'),
+             'tourism:attraction' => array('label' => 'Attraction', 'frequency' => 1463, 'icon' => 'poi_point_of_interest'),
+             'leisure:golf_course' => array('label' => 'Golf Course', 'frequency' => 712, 'icon' => 'sport_golf'),
+             'historic:castle' => array('label' => 'Castle', 'frequency' => 316, 'icon' => 'tourist_castle'),
+             'amenity:hospital' => array('label' => 'Hospital', 'frequency' => 879, 'icon' => 'health_hospital'),
+             'amenity:school' => array('label' => 'School', 'frequency' => 8192, 'icon' => 'education_school'),
+             'amenity:theatre' => array('label' => 'Theatre', 'frequency' => 371, 'icon' => 'tourist_theatre'),
+             'amenity:public_building' => array('label' => 'Public Building', 'frequency' => 985, 'icon' => ''),
+             'amenity:library' => array('label' => 'Library', 'frequency' => 794, 'icon' => 'amenity_library'),
+             'amenity:townhall' => array('label' => 'Townhall', 'frequency' => 242, 'icon' => ''),
+             'amenity:community_centre' => array('label' => 'Community Centre', 'frequency' => 157, 'icon' => ''),
+             'amenity:fire_station' => array('label' => 'Fire Station', 'frequency' => 221, 'icon' => 'amenity_firestation3'),
+             'amenity:police' => array('label' => 'Police', 'frequency' => 334, 'icon' => 'amenity_police2'),
+             'amenity:bank' => array('label' => 'Bank', 'frequency' => 1248, 'icon' => 'money_bank2'),
+             'amenity:post_office' => array('label' => 'Post Office', 'frequency' => 859, 'icon' => 'amenity_post_office'),
+             'leisure:park' => array('label' => 'Park', 'frequency' => 2378, 'icon' => ''),
+             'amenity:park' => array('label' => 'Park', 'frequency' => 53, 'icon' => ''),
+             'landuse:park' => array('label' => 'Park', 'frequency' => 50, 'icon' => ''),
+             'landuse:recreation_ground' => array('label' => 'Recreation Ground', 'frequency' => 517, 'icon' => ''),
+             'tourism:hotel' => array('label' => 'Hotel', 'frequency' => 2150, 'icon' => 'accommodation_hotel2'),
+             'tourism:motel' => array('label' => 'Motel', 'frequency' => 43, 'icon' => ''),
+             'amenity:cinema' => array('label' => 'Cinema', 'frequency' => 277, 'icon' => 'tourist_cinema'),
+             'tourism:artwork' => array('label' => 'Artwork', 'frequency' => 171, 'icon' => 'tourist_art_gallery2'),
+             'historic:archaeological_site' => array('label' => 'Archaeological Site', 'frequency' => 407, 'icon' => 'tourist_archaeological2'),
+             'amenity:doctors' => array('label' => 'Doctors', 'frequency' => 581, 'icon' => 'health_doctors'),
+             'leisure:sports_centre' => array('label' => 'Sports Centre', 'frequency' => 767, 'icon' => 'sport_leisure_centre'),
+             'leisure:swimming_pool' => array('label' => 'Swimming Pool', 'frequency' => 24, 'icon' => 'sport_swimming_outdoor'),
+             'shop:supermarket' => array('label' => 'Supermarket', 'frequency' => 2673, 'icon' => 'shopping_supermarket'),
+             'shop:convenience' => array('label' => 'Convenience', 'frequency' => 1469, 'icon' => 'shopping_convenience'),
+             'amenity:restaurant' => array('label' => 'Restaurant', 'frequency' => 3179, 'icon' => 'food_restaurant'),
+             'amenity:fast_food' => array('label' => 'Fast Food', 'frequency' => 2289, 'icon' => 'food_fastfood'),
+             'amenity:cafe' => array('label' => 'Cafe', 'frequency' => 1780, 'icon' => 'food_cafe'),
+             'tourism:guest_house' => array('label' => 'Guest House', 'frequency' => 223, 'icon' => 'accommodation_bed_and_breakfast'),
+             'amenity:pharmacy' => array('label' => 'Pharmacy', 'frequency' => 733, 'icon' => 'health_pharmacy_dispensing'),
+             'amenity:fuel' => array('label' => 'Fuel', 'frequency' => 1308, 'icon' => 'transport_fuel'),
+             'natural:peak' => array('label' => 'Peak', 'frequency' => 3212, 'icon' => 'poi_peak'),
+             'waterway:waterfall' => array('label' => 'Waterfall', 'frequency' => 24, 'icon' => ''),
+             'natural:wood' => array('label' => 'Wood', 'frequency' => 1845, 'icon' => 'landuse_coniferous_and_deciduous'),
+             'natural:water' => array('label' => 'Water', 'frequency' => 1790, 'icon' => ''),
+             'landuse:forest' => array('label' => 'Forest', 'frequency' => 467, 'icon' => ''),
+             'landuse:cemetery' => array('label' => 'Cemetery', 'frequency' => 463, 'icon' => ''),
+             'landuse:allotments' => array('label' => 'Allotments', 'frequency' => 408, 'icon' => ''),
+             'landuse:farmyard' => array('label' => 'Farmyard', 'frequency' => 397, 'icon' => ''),
+             'railway:rail' => array('label' => 'Rail', 'frequency' => 4894, 'icon' => ''),
+             'waterway:canal' => array('label' => 'Canal', 'frequency' => 1723, 'icon' => ''),
+             'waterway:river' => array('label' => 'River', 'frequency' => 4089, 'icon' => ''),
+             'waterway:stream' => array('label' => 'Stream', 'frequency' => 2684, 'icon' => ''),
+             'shop:bicycle' => array('label' => 'Bicycle', 'frequency' => 349, 'icon' => 'shopping_bicycle'),
+             'shop:clothes' => array('label' => 'Clothes', 'frequency' => 315, 'icon' => 'shopping_clothes'),
+             'shop:hairdresser' => array('label' => 'Hairdresser', 'frequency' => 312, 'icon' => 'shopping_hairdresser'),
+             'shop:doityourself' => array('label' => 'Doityourself', 'frequency' => 247, 'icon' => 'shopping_diy'),
+             'shop:estate_agent' => array('label' => 'Estate Agent', 'frequency' => 162, 'icon' => 'shopping_estateagent2'),
+             'shop:car' => array('label' => 'Car', 'frequency' => 159, 'icon' => 'shopping_car'),
+             'shop:garden_centre' => array('label' => 'Garden Centre', 'frequency' => 143, 'icon' => 'shopping_garden_centre'),
+             'shop:car_repair' => array('label' => 'Car Repair', 'frequency' => 141, 'icon' => 'shopping_car_repair'),
+             'shop:newsagent' => array('label' => 'Newsagent', 'frequency' => 132, 'icon' => ''),
+             'shop:bakery' => array('label' => 'Bakery', 'frequency' => 129, 'icon' => 'shopping_bakery'),
+             'shop:furniture' => array('label' => 'Furniture', 'frequency' => 124, 'icon' => ''),
+             'shop:butcher' => array('label' => 'Butcher', 'frequency' => 105, 'icon' => 'shopping_butcher'),
+             'shop:apparel' => array('label' => 'Apparel', 'frequency' => 98, 'icon' => 'shopping_clothes'),
+             'shop:electronics' => array('label' => 'Electronics', 'frequency' => 96, 'icon' => ''),
+             'shop:department_store' => array('label' => 'Department Store', 'frequency' => 86, 'icon' => ''),
+             'shop:books' => array('label' => 'Books', 'frequency' => 85, 'icon' => ''),
+             'shop:yes' => array('label' => 'Shop', 'frequency' => 68, 'icon' => ''),
+             'shop:outdoor' => array('label' => 'Outdoor', 'frequency' => 67, 'icon' => ''),
+             'shop:mall' => array('label' => 'Mall', 'frequency' => 63, 'icon' => ''),
+             'shop:florist' => array('label' => 'Florist', 'frequency' => 61, 'icon' => ''),
+             'shop:charity' => array('label' => 'Charity', 'frequency' => 60, 'icon' => ''),
+             'shop:hardware' => array('label' => 'Hardware', 'frequency' => 59, 'icon' => ''),
+             'shop:laundry' => array('label' => 'Laundry', 'frequency' => 51, 'icon' => 'shopping_laundrette'),
+             'shop:shoes' => array('label' => 'Shoes', 'frequency' => 49, 'icon' => ''),
+             'shop:beverages' => array('label' => 'Beverages', 'frequency' => 48, 'icon' => 'shopping_alcohol'),
+             'shop:dry_cleaning' => array('label' => 'Dry Cleaning', 'frequency' => 46, 'icon' => ''),
+             'shop:carpet' => array('label' => 'Carpet', 'frequency' => 45, 'icon' => ''),
+             'shop:computer' => array('label' => 'Computer', 'frequency' => 44, 'icon' => ''),
+             'shop:alcohol' => array('label' => 'Alcohol', 'frequency' => 44, 'icon' => 'shopping_alcohol'),
+             'shop:optician' => array('label' => 'Optician', 'frequency' => 55, 'icon' => 'health_opticians'),
+             'shop:chemist' => array('label' => 'Chemist', 'frequency' => 42, 'icon' => 'health_pharmacy'),
+             'shop:gallery' => array('label' => 'Gallery', 'frequency' => 38, 'icon' => 'tourist_art_gallery2'),
+             'shop:mobile_phone' => array('label' => 'Mobile Phone', 'frequency' => 37, 'icon' => ''),
+             'shop:sports' => array('label' => 'Sports', 'frequency' => 37, 'icon' => ''),
+             'shop:jewelry' => array('label' => 'Jewelry', 'frequency' => 32, 'icon' => 'shopping_jewelry'),
+             'shop:pet' => array('label' => 'Pet', 'frequency' => 29, 'icon' => ''),
+             'shop:beauty' => array('label' => 'Beauty', 'frequency' => 28, 'icon' => ''),
+             'shop:stationery' => array('label' => 'Stationery', 'frequency' => 25, 'icon' => ''),
+             'shop:shopping_centre' => array('label' => 'Shopping Centre', 'frequency' => 25, 'icon' => ''),
+             'shop:general' => array('label' => 'General', 'frequency' => 25, 'icon' => ''),
+             'shop:electrical' => array('label' => 'Electrical', 'frequency' => 25, 'icon' => ''),
+             'shop:toys' => array('label' => 'Toys', 'frequency' => 23, 'icon' => ''),
+             'shop:jeweller' => array('label' => 'Jeweller', 'frequency' => 23, 'icon' => ''),
+             'shop:betting' => array('label' => 'Betting', 'frequency' => 23, 'icon' => ''),
+             'shop:household' => array('label' => 'Household', 'frequency' => 21, 'icon' => ''),
+             'shop:travel_agency' => array('label' => 'Travel Agency', 'frequency' => 21, 'icon' => ''),
+             'shop:hifi' => array('label' => 'Hifi', 'frequency' => 21, 'icon' => ''),
+             'amenity:shop' => array('label' => 'Shop', 'frequency' => 61, 'icon' => ''),
+             'tourism:information' => array('label' => 'Information', 'frequency' => 224, 'icon' => 'amenity_information'),
+ 
+             'place:house' => array('label' => 'House', 'frequency' => 2086, 'icon' => '', 'defzoom' => 18),
+             'place:house_name' => array('label' => 'House', 'frequency' => 2086, 'icon' => '', 'defzoom' => 18),
+             'place:house_number' => array('label' => 'House Number', 'frequency' => 2086, 'icon' => '', 'defzoom' => 18),
+             'place:country_code' => array('label' => 'Country Code', 'frequency' => 2086, 'icon' => '', 'defzoom' => 18),
+ 
+             //
+ 
+             'leisure:pitch' => array('label' => 'Pitch', 'frequency' => 762, 'icon' => ''),
+             'highway:unsurfaced' => array('label' => 'Unsurfaced', 'frequency' => 492, 'icon' => ''),
+             'historic:ruins' => array('label' => 'Ruins', 'frequency' => 483, 'icon' => 'tourist_ruin'),
+             'amenity:college' => array('label' => 'College', 'frequency' => 473, 'icon' => 'education_school'),
+             'historic:monument' => array('label' => 'Monument', 'frequency' => 470, 'icon' => 'tourist_monument'),
+             'railway:subway' => array('label' => 'Subway', 'frequency' => 385, 'icon' => ''),
+             'historic:memorial' => array('label' => 'Memorial', 'frequency' => 382, 'icon' => 'tourist_monument'),
+             'leisure:nature_reserve' => array('label' => 'Nature Reserve', 'frequency' => 342, 'icon' => ''),
+             'leisure:common' => array('label' => 'Common', 'frequency' => 322, 'icon' => ''),
+             'waterway:lock_gate' => array('label' => 'Lock Gate', 'frequency' => 321, 'icon' => ''),
+             'natural:fell' => array('label' => 'Fell', 'frequency' => 308, 'icon' => ''),
+             'amenity:nightclub' => array('label' => 'Nightclub', 'frequency' => 292, 'icon' => ''),
+             'highway:path' => array('label' => 'Path', 'frequency' => 287, 'icon' => ''),
+             'leisure:garden' => array('label' => 'Garden', 'frequency' => 285, 'icon' => ''),
+             'landuse:reservoir' => array('label' => 'Reservoir', 'frequency' => 276, 'icon' => ''),
+             'leisure:playground' => array('label' => 'Playground', 'frequency' => 264, 'icon' => ''),
+             'leisure:stadium' => array('label' => 'Stadium', 'frequency' => 212, 'icon' => ''),
+             'historic:mine' => array('label' => 'Mine', 'frequency' => 193, 'icon' => 'poi_mine'),
+             'natural:cliff' => array('label' => 'Cliff', 'frequency' => 193, 'icon' => ''),
+             'tourism:caravan_site' => array('label' => 'Caravan Site', 'frequency' => 183, 'icon' => 'accommodation_caravan_park'),
+             'amenity:bus_station' => array('label' => 'Bus Station', 'frequency' => 181, 'icon' => 'transport_bus_station'),
+             'amenity:kindergarten' => array('label' => 'Kindergarten', 'frequency' => 179, 'icon' => ''),
+             'highway:construction' => array('label' => 'Construction', 'frequency' => 176, 'icon' => ''),
+             'amenity:atm' => array('label' => 'Atm', 'frequency' => 172, 'icon' => 'money_atm2'),
+             'amenity:emergency_phone' => array('label' => 'Emergency Phone', 'frequency' => 164, 'icon' => ''),
+             'waterway:lock' => array('label' => 'Lock', 'frequency' => 146, 'icon' => ''),
+             'waterway:riverbank' => array('label' => 'Riverbank', 'frequency' => 143, 'icon' => ''),
+             'natural:coastline' => array('label' => 'Coastline', 'frequency' => 142, 'icon' => ''),
+             'tourism:viewpoint' => array('label' => 'Viewpoint', 'frequency' => 140, 'icon' => 'tourist_view_point'),
+             'tourism:hostel' => array('label' => 'Hostel', 'frequency' => 140, 'icon' => ''),
+             'tourism:bed_and_breakfast' => array('label' => 'Bed And Breakfast', 'frequency' => 140, 'icon' => 'accommodation_bed_and_breakfast'),
+             'railway:halt' => array('label' => 'Halt', 'frequency' => 135, 'icon' => ''),
+             'railway:platform' => array('label' => 'Platform', 'frequency' => 134, 'icon' => ''),
+             'railway:tram' => array('label' => 'Tram', 'frequency' => 130, 'icon' => 'transport_tram_stop'),
+             'amenity:courthouse' => array('label' => 'Courthouse', 'frequency' => 129, 'icon' => 'amenity_court'),
+             'amenity:recycling' => array('label' => 'Recycling', 'frequency' => 126, 'icon' => 'amenity_recycling'),
+             'amenity:dentist' => array('label' => 'Dentist', 'frequency' => 124, 'icon' => 'health_dentist'),
+             'natural:beach' => array('label' => 'Beach', 'frequency' => 121, 'icon' => 'tourist_beach'),
+             'place:moor' => array('label' => 'Moor', 'frequency' => 118, 'icon' => ''),
+             'amenity:grave_yard' => array('label' => 'Grave Yard', 'frequency' => 110, 'icon' => ''),
+             'waterway:drain' => array('label' => 'Drain', 'frequency' => 108, 'icon' => ''),
+             'landuse:grass' => array('label' => 'Grass', 'frequency' => 106, 'icon' => ''),
+             'landuse:village_green' => array('label' => 'Village Green', 'frequency' => 106, 'icon' => ''),
+             'natural:bay' => array('label' => 'Bay', 'frequency' => 102, 'icon' => ''),
+             'railway:tram_stop' => array('label' => 'Tram Stop', 'frequency' => 101, 'icon' => 'transport_tram_stop'),
+             'leisure:marina' => array('label' => 'Marina', 'frequency' => 98, 'icon' => ''),
+             'highway:stile' => array('label' => 'Stile', 'frequency' => 97, 'icon' => ''),
+             'natural:moor' => array('label' => 'Moor', 'frequency' => 95, 'icon' => ''),
+             'railway:light_rail' => array('label' => 'Light Rail', 'frequency' => 91, 'icon' => ''),
+             'railway:narrow_gauge' => array('label' => 'Narrow Gauge', 'frequency' => 90, 'icon' => ''),
+             'natural:land' => array('label' => 'Land', 'frequency' => 86, 'icon' => ''),
+             'amenity:village_hall' => array('label' => 'Village Hall', 'frequency' => 82, 'icon' => ''),
+             'waterway:dock' => array('label' => 'Dock', 'frequency' => 80, 'icon' => ''),
+             'amenity:veterinary' => array('label' => 'Veterinary', 'frequency' => 79, 'icon' => ''),
+             'landuse:brownfield' => array('label' => 'Brownfield', 'frequency' => 77, 'icon' => ''),
+             'leisure:track' => array('label' => 'Track', 'frequency' => 76, 'icon' => ''),
+             'railway:historic_station' => array('label' => 'Historic Station', 'frequency' => 74, 'icon' => ''),
+             'landuse:construction' => array('label' => 'Construction', 'frequency' => 72, 'icon' => ''),
+             'amenity:prison' => array('label' => 'Prison', 'frequency' => 71, 'icon' => 'amenity_prison'),
+             'landuse:quarry' => array('label' => 'Quarry', 'frequency' => 71, 'icon' => ''),
+             'amenity:telephone' => array('label' => 'Telephone', 'frequency' => 70, 'icon' => ''),
+             'highway:traffic_signals' => array('label' => 'Traffic Signals', 'frequency' => 66, 'icon' => ''),
+             'natural:heath' => array('label' => 'Heath', 'frequency' => 62, 'icon' => ''),
+             'historic:house' => array('label' => 'House', 'frequency' => 61, 'icon' => ''),
+             'amenity:social_club' => array('label' => 'Social Club', 'frequency' => 61, 'icon' => ''),
+             'landuse:military' => array('label' => 'Military', 'frequency' => 61, 'icon' => ''),
+             'amenity:health_centre' => array('label' => 'Health Centre', 'frequency' => 59, 'icon' => ''),
+             'historic:building' => array('label' => 'Building', 'frequency' => 58, 'icon' => ''),
+             'amenity:clinic' => array('label' => 'Clinic', 'frequency' => 57, 'icon' => ''),
+             'highway:services' => array('label' => 'Services', 'frequency' => 56, 'icon' => ''),
+             'amenity:ferry_terminal' => array('label' => 'Ferry Terminal', 'frequency' => 55, 'icon' => ''),
+             'natural:marsh' => array('label' => 'Marsh', 'frequency' => 55, 'icon' => ''),
+             'natural:hill' => array('label' => 'Hill', 'frequency' => 54, 'icon' => ''),
+             'highway:raceway' => array('label' => 'Raceway', 'frequency' => 53, 'icon' => ''),
+             'amenity:taxi' => array('label' => 'Taxi', 'frequency' => 47, 'icon' => ''),
+             'amenity:take_away' => array('label' => 'Take Away', 'frequency' => 45, 'icon' => ''),
+             'amenity:car_rental' => array('label' => 'Car Rental', 'frequency' => 44, 'icon' => ''),
+             'place:islet' => array('label' => 'Islet', 'frequency' => 44, 'icon' => ''),
+             'amenity:nursery' => array('label' => 'Nursery', 'frequency' => 44, 'icon' => ''),
+             'amenity:nursing_home' => array('label' => 'Nursing Home', 'frequency' => 43, 'icon' => ''),
+             'amenity:toilets' => array('label' => 'Toilets', 'frequency' => 38, 'icon' => ''),
+             'amenity:hall' => array('label' => 'Hall', 'frequency' => 38, 'icon' => ''),
+             'waterway:boatyard' => array('label' => 'Boatyard', 'frequency' => 36, 'icon' => ''),
+             'highway:mini_roundabout' => array('label' => 'Mini Roundabout', 'frequency' => 35, 'icon' => ''),
+             'historic:manor' => array('label' => 'Manor', 'frequency' => 35, 'icon' => ''),
+             'tourism:chalet' => array('label' => 'Chalet', 'frequency' => 34, 'icon' => ''),
+             'amenity:bicycle_parking' => array('label' => 'Bicycle Parking', 'frequency' => 34, 'icon' => ''),
+             'amenity:hotel' => array('label' => 'Hotel', 'frequency' => 34, 'icon' => ''),
+             'waterway:weir' => array('label' => 'Weir', 'frequency' => 33, 'icon' => ''),
+             'natural:wetland' => array('label' => 'Wetland', 'frequency' => 33, 'icon' => ''),
+             'natural:cave_entrance' => array('label' => 'Cave Entrance', 'frequency' => 32, 'icon' => ''),
+             'amenity:crematorium' => array('label' => 'Crematorium', 'frequency' => 31, 'icon' => ''),
+             'tourism:picnic_site' => array('label' => 'Picnic Site', 'frequency' => 31, 'icon' => ''),
+             'landuse:wood' => array('label' => 'Wood', 'frequency' => 30, 'icon' => ''),
+             'landuse:basin' => array('label' => 'Basin', 'frequency' => 30, 'icon' => ''),
+             'natural:tree' => array('label' => 'Tree', 'frequency' => 30, 'icon' => ''),
+             'leisure:slipway' => array('label' => 'Slipway', 'frequency' => 29, 'icon' => ''),
+             'landuse:meadow' => array('label' => 'Meadow', 'frequency' => 29, 'icon' => ''),
+             'landuse:piste' => array('label' => 'Piste', 'frequency' => 28, 'icon' => ''),
+             'amenity:care_home' => array('label' => 'Care Home', 'frequency' => 28, 'icon' => ''),
+             'amenity:club' => array('label' => 'Club', 'frequency' => 28, 'icon' => ''),
+             'amenity:medical_centre' => array('label' => 'Medical Centre', 'frequency' => 27, 'icon' => ''),
+             'historic:roman_road' => array('label' => 'Roman Road', 'frequency' => 27, 'icon' => ''),
+             'historic:fort' => array('label' => 'Fort', 'frequency' => 26, 'icon' => ''),
+             'railway:subway_entrance' => array('label' => 'Subway Entrance', 'frequency' => 26, 'icon' => ''),
+             'historic:yes' => array('label' => 'Historic', 'frequency' => 25, 'icon' => ''),
+             'highway:gate' => array('label' => 'Gate', 'frequency' => 25, 'icon' => ''),
+             'leisure:fishing' => array('label' => 'Fishing', 'frequency' => 24, 'icon' => ''),
+             'historic:museum' => array('label' => 'Museum', 'frequency' => 24, 'icon' => ''),
+             'amenity:car_wash' => array('label' => 'Car Wash', 'frequency' => 24, 'icon' => ''),
+             'railway:level_crossing' => array('label' => 'Level Crossing', 'frequency' => 23, 'icon' => ''),
+             'leisure:bird_hide' => array('label' => 'Bird Hide', 'frequency' => 23, 'icon' => ''),
+             'natural:headland' => array('label' => 'Headland', 'frequency' => 21, 'icon' => ''),
+             'tourism:apartments' => array('label' => 'Apartments', 'frequency' => 21, 'icon' => ''),
+             'amenity:shopping' => array('label' => 'Shopping', 'frequency' => 21, 'icon' => ''),
+             'natural:scrub' => array('label' => 'Scrub', 'frequency' => 20, 'icon' => ''),
+             'natural:fen' => array('label' => 'Fen', 'frequency' => 20, 'icon' => ''),
+             'building:yes' => array('label' => 'Building', 'frequency' => 200, 'icon' => ''),
+             'mountain_pass:yes' => array('label' => 'Mountain Pass', 'frequency' => 200, 'icon' => ''),
+ 
+             'amenity:parking' => array('label' => 'Parking', 'frequency' => 3157, 'icon' => ''),
+             'highway:bus_stop' => array('label' => 'Bus Stop', 'frequency' => 35777, 'icon' => 'transport_bus_stop2'),
+             'place:postcode' => array('label' => 'Postcode', 'frequency' => 27267, 'icon' => ''),
+             'amenity:post_box' => array('label' => 'Post Box', 'frequency' => 9613, 'icon' => ''),
+ 
+             'place:houses' => array('label' => 'Houses', 'frequency' => 85, 'icon' => ''),
+             'railway:preserved' => array('label' => 'Preserved', 'frequency' => 227, 'icon' => ''),
+             'waterway:derelict_canal' => array('label' => 'Derelict Canal', 'frequency' => 21, 'icon' => ''),
+             'amenity:dead_pub' => array('label' => 'Dead Pub', 'frequency' => 20, 'icon' => ''),
+             'railway:disused_station' => array('label' => 'Disused Station', 'frequency' => 114, 'icon' => ''),
+             'railway:abandoned' => array('label' => 'Abandoned', 'frequency' => 641, 'icon' => ''),
+             'railway:disused' => array('label' => 'Disused', 'frequency' => 72, 'icon' => ''),
+            );
+ }
+ 
+ 
+ function getClassTypesWithImportance()
+ {
+     $aOrders = getClassTypes();
+     $i = 1;
+     foreach ($aOrders as $sID => $a) {
+         $aOrders[$sID]['importance'] = $i++;
+     }
+     return $aOrders;
+ }
+ 
+ function getResultDiameter($aResult)
+ {
+     $aClassType = getClassTypes();
+ 
+     $fDiameter = 0.0001;
+ 
+     if (isset($aResult['class'])
+         && isset($aResult['type'])
+         && isset($aResult['admin_level'])
+         && isset($aClassType[$aResult['class'].':'.$aResult['type'].':'.$aResult['admin_level']]['defdiameter'])
+         && $aClassType[$aResult['class'].':'.$aResult['type'].':'.$aResult['admin_level']]['defdiameter']
+     ) {
+         $fDiameter = $aClassType[$aResult['class'].':'.$aResult['type'].':'.$aResult['admin_level']]['defdiameter'];
+     } elseif (isset($aResult['class'])
+         && isset($aResult['type'])
+         && isset($aClassType[$aResult['class'].':'.$aResult['type']]['defdiameter'])
+         && $aClassType[$aResult['class'].':'.$aResult['type']]['defdiameter']
+     ) {
+         $fDiameter = $aClassType[$aResult['class'].':'.$aResult['type']]['defdiameter'];
+     }
+ 
+     return $fDiameter;
+ }
+ 
+ 
+ function javascript_renderData($xVal, $iOptions = 0)
+ {
+     if (defined('PHP_VERSION_ID') && PHP_VERSION_ID > 50400)
+         $iOptions |= JSON_UNESCAPED_UNICODE;
+     $jsonout = json_encode($xVal, $iOptions);
+ 
+     if (! isset($_GET['json_callback'])) {
+         header("Content-Type: application/json; charset=UTF-8");
+         echo $jsonout;
+     } else {
+         if (preg_match('/^[$_\p{L}][$_\p{L}\p{Nd}.[\]]*$/u', $_GET['json_callback'])) {
+             header("Content-Type: application/javascript; charset=UTF-8");
+             echo $_GET['json_callback'].'('.$jsonout.')';
+         } else {
+             header('HTTP/1.0 400 Bad Request');
+         }
+     }
+ }
+ 
+ 
+ function _debugDumpGroupedSearches($aData, $aTokens)
+ {
+     $aWordsIDs = array();
+     if ($aTokens) {
+         foreach ($aTokens as $sToken => $aWords) {
+             if ($aWords) {
+                 foreach ($aWords as $aToken) {
+                     $aWordsIDs[$aToken['word_id']] = $sToken.'('.$aToken['word_id'].')';
+                 }
+             }
+         }
+     }
+     echo "";
+     echo "";
+     foreach ($aData as $iRank => $aRankedSet) {
+         foreach ($aRankedSet as $aRow) {
+             echo "";
+             echo "";
+ 
+             echo "";
+ 
+             echo "";
+ 
+             echo "";
+ 
+             echo "";
+ 
+             echo "";
+ 
+             echo "";
+             echo "";
+             echo "";
+ 
+             echo "";
+ 
+             echo "";
+             echo "";
+             echo "";
+ 
+             echo "";
+         }
+     }
+     echo "
rankName TokensName NotAddress TokensAddress Notcountryoperatorclasstypehouse#LatLonRadius
$iRank"; + $sSep = ''; + foreach ($aRow['aName'] as $iWordID) { + echo $sSep.'#'.$aWordsIDs[$iWordID].'#'; + $sSep = ', '; + } + echo ""; + $sSep = ''; + foreach ($aRow['aNameNonSearch'] as $iWordID) { + echo $sSep.'#'.$aWordsIDs[$iWordID].'#'; + $sSep = ', '; + } + echo ""; + $sSep = ''; + foreach ($aRow['aAddress'] as $iWordID) { + echo $sSep.'#'.$aWordsIDs[$iWordID].'#'; + $sSep = ', '; + } + echo ""; + $sSep = ''; + foreach ($aRow['aAddressNonSearch'] as $iWordID) { + echo $sSep.'#'.$aWordsIDs[$iWordID].'#'; + $sSep = ', '; + } + echo "".$aRow['sCountryCode']."".$aRow['sOperator']."".$aRow['sClass']."".$aRow['sType']."".$aRow['sHouseNumber']."".$aRow['fLat']."".$aRow['fLon']."".$aRow['fRadius']."
"; + } + + + function getAddressDetails(&$oDB, $sLanguagePrefArraySQL, $iPlaceID, $sCountryCode = false, $housenumber = -1, $bRaw = false) + { + $sSQL = "select *,get_name_by_language(name,$sLanguagePrefArraySQL) as localname from get_addressdata($iPlaceID, $housenumber)"; + if (!$bRaw) $sSQL .= " WHERE isaddress OR type = 'country_code'"; + $sSQL .= " order by rank_address desc,isaddress desc"; + + $aAddressLines = chksql($oDB->getAll($sSQL)); + if ($bRaw) return $aAddressLines; + //echo "
";
+     //var_dump($aAddressLines);
+     $aAddress = array();
+     $aFallback = array();
+     $aClassType = getClassTypes();
+     foreach ($aAddressLines as $aLine) {
+         $bFallback = false;
+         $aTypeLabel = false;
+         if (isset($aClassType[$aLine['class'].':'.$aLine['type'].':'.$aLine['admin_level']])) {
+             $aTypeLabel = $aClassType[$aLine['class'].':'.$aLine['type'].':'.$aLine['admin_level']];
+         } elseif (isset($aClassType[$aLine['class'].':'.$aLine['type']])) {
+             $aTypeLabel = $aClassType[$aLine['class'].':'.$aLine['type']];
+         } elseif (isset($aClassType['boundary:administrative:'.((int)($aLine['rank_address']/2))])) {
+             $aTypeLabel = $aClassType['boundary:administrative:'.((int)($aLine['rank_address']/2))];
+             $bFallback = true;
+         } else {
+             $aTypeLabel = array('simplelabel' => 'address'.$aLine['rank_address']);
+             $bFallback = true;
+         }
+         if ($aTypeLabel && ((isset($aLine['localname']) && $aLine['localname']) || (isset($aLine['housenumber']) && $aLine['housenumber']))) {
+             $sTypeLabel = strtolower(isset($aTypeLabel['simplelabel'])?$aTypeLabel['simplelabel']:$aTypeLabel['label']);
+             $sTypeLabel = str_replace(' ', '_', $sTypeLabel);
+             if (!isset($aAddress[$sTypeLabel]) || (isset($aFallback[$sTypeLabel]) && $aFallback[$sTypeLabel]) || $aLine['class'] == 'place') {
+                 $aAddress[$sTypeLabel] = $aLine['localname']?$aLine['localname']:$aLine['housenumber'];
+             }
+             $aFallback[$sTypeLabel] = $bFallback;
+         }
+     }
+     return $aAddress;
+ }
+ 
+ 
+ function addQuotes($s)
+ {
+     return "'".$s."'";
+ }
+ 
+ function validLatLon($fLat, $fLon)
+ {
+     return ($fLat <= 90.1 && $fLat >= -90.1 && $fLon <= 180.1 && $fLon >= -180.1);
+ }
+ 
+ function looksLikeLatLonPair($sQuery)
+ {
+     // Do we have anything that looks like a lat/lon pair?
+     // returns array(lat,lon,query_with_lat_lon_removed)
+     // or null
+     $sFound    = null;
+     $fQueryLat = null;
+     $fQueryLon = null;
+ 
+     if (preg_match('/\\b([NS])[ ]+([0-9]+[0-9.]*)[° ]+([0-9.]+)?[′\']*[, ]+([EW])[ ]+([0-9]+)[° ]+([0-9]+[0-9.]*)[′\']*?\\b/', $sQuery, $aData)) {
+         /*              1         2                   3                  4         5            6
+          * degrees decimal minutes
+          * N 40 26.767, W 79 58.933
+          * N 40°26.767′, W 79°58.933′
+          */
+         $sFound    = $aData[0];
+         $fQueryLat = ($aData[1]=='N'?1:-1) * ($aData[2] + $aData[3]/60);
+         $fQueryLon = ($aData[4]=='E'?1:-1) * ($aData[5] + $aData[6]/60);
+     } elseif (preg_match('/\\b([0-9]+)[° ]+([0-9]+[0-9.]*)?[′\']*[ ]+([NS])[, ]+([0-9]+)[° ]+([0-9]+[0-9.]*)?[′\' ]+([EW])\\b/', $sQuery, $aData)) {
+         /*                    1             2                      3          4            5                    6
+          * degrees decimal minutes
+          * 40 26.767 N, 79 58.933 W
+          * 40° 26.767′ N 79° 58.933′ W
+          */
+         $sFound    = $aData[0];
+         $fQueryLat = ($aData[3]=='N'?1:-1) * ($aData[1] + $aData[2]/60);
+         $fQueryLon = ($aData[6]=='E'?1:-1) * ($aData[4] + $aData[5]/60);
+     } elseif (preg_match('/\\b([NS])[ ]([0-9]+)[° ]+([0-9]+)[′\' ]+([0-9]+)[″"]*[, ]+([EW])[ ]([0-9]+)[° ]+([0-9]+)[′\' ]+([0-9]+)[″"]*\\b/', $sQuery, $aData)) {
+         /*                    1        2            3            4                5        6            7            8
+          * degrees decimal seconds
+          * N 40 26 46 W 79 58 56
+          * N 40° 26′ 46″, W 79° 58′ 56″
+          */
+         $sFound    = $aData[0];
+         $fQueryLat = ($aData[1]=='N'?1:-1) * ($aData[2] + $aData[3]/60 + $aData[4]/3600);
+         $fQueryLon = ($aData[5]=='E'?1:-1) * ($aData[6] + $aData[7]/60 + $aData[8]/3600);
+     } elseif (preg_match('/\\b([0-9]+)[° ]+([0-9]+)[′\' ]+([0-9]+)[″" ]+([NS])[, ]+([0-9]+)[° ]+([0-9]+)[′\' ]+([0-9]+)[″" ]+([EW])\\b/', $sQuery, $aData)) {
+         /*                    1            2            3            4          5            6            7            8
+          * degrees decimal seconds
+          * 40 26 46 N 79 58 56 W
+          * 40° 26′ 46″ N, 79° 58′ 56″ W
+          */
+         $sFound    = $aData[0];
+         $fQueryLat = ($aData[4]=='N'?1:-1) * ($aData[1] + $aData[2]/60 + $aData[3]/3600);
+         $fQueryLon = ($aData[8]=='E'?1:-1) * ($aData[5] + $aData[6]/60 + $aData[7]/3600);
+     } elseif (preg_match('/\\b([NS])[ ]([0-9]+[0-9]*\\.[0-9]+)[°]*[, ]+([EW])[ ]([0-9]+[0-9]*\\.[0-9]+)[°]*\\b/', $sQuery, $aData)) {
+         /*                    1        2                               3        4
+          * degrees decimal
+          * N 40.446° W 79.982°
+          */
+         $sFound    = $aData[0];
+         $fQueryLat = ($aData[1]=='N'?1:-1) * ($aData[2]);
+         $fQueryLon = ($aData[3]=='E'?1:-1) * ($aData[4]);
+     } elseif (preg_match('/\\b([0-9]+[0-9]*\\.[0-9]+)[° ]+([NS])[, ]+([0-9]+[0-9]*\\.[0-9]+)[° ]+([EW])\\b/', $sQuery, $aData)) {
+         /*                    1                           2          3                           4
+          * degrees decimal
+          * 40.446° N 79.982° W
+          */
+         $sFound    = $aData[0];
+         $fQueryLat = ($aData[2]=='N'?1:-1) * ($aData[1]);
+         $fQueryLon = ($aData[4]=='E'?1:-1) * ($aData[3]);
+     } elseif (preg_match('/(\\[|^|\\b)(-?[0-9]+[0-9]*\\.[0-9]+)[, ]+(-?[0-9]+[0-9]*\\.[0-9]+)(\\]|$|\\b)/', $sQuery, $aData)) {
+         /*                 1          2                             3                        4
+          * degrees decimal
+          * 12.34, 56.78
+          * [12.456,-78.90]
+          */
+         $sFound    = $aData[0];
+         $fQueryLat = $aData[2];
+         $fQueryLon = $aData[3];
+     }
+ 
+     if (!validLatLon($fQueryLat, $fQueryLon)) return;
+     $sQuery = trim(str_replace($sFound, ' ', $sQuery));
+ 
+     return array('lat' => $fQueryLat, 'lon' => $fQueryLon, 'query' => $sQuery);
+ }
+ 
+ 
+ function geometryText2Points($geometry_as_text, $fRadius)
+ {
+     $aPolyPoints = null;
+     if (preg_match('#POLYGON\\(\\(([- 0-9.,]+)#', $geometry_as_text, $aMatch)) {
+         //
+         preg_match_all('/(-?[0-9.]+) (-?[0-9.]+)/', $aMatch[1], $aPolyPoints, PREG_SET_ORDER);
+         //
+     } elseif (preg_match('#LINESTRING\\(([- 0-9.,]+)#', $geometry_as_text, $aMatch)) {
+         //
+         preg_match_all('/(-?[0-9.]+) (-?[0-9.]+)/', $aMatch[1], $aPolyPoints, PREG_SET_ORDER);
+         //
 -    } elseif (preg_match('#MULTIPOLYGON\\(\\(\\(([- 0-9.,]+)#', $geometry_as_text, $aMatch)) {
++/*    } elseif (preg_match('#MULTIPOLYGON\\(\\(\\(([- 0-9.,]+)#', $geometry_as_text, $aMatch)) {
+         //
+         preg_match_all('/(-?[0-9.]+) (-?[0-9.]+)/', $aMatch[1], $aPolyPoints, PREG_SET_ORDER);
 -        //
++        */
+     } elseif (preg_match('#POINT\\((-?[0-9.]+) (-?[0-9.]+)\\)#', $geometry_as_text, $aMatch)) {
+         //
+         $aPolyPoints = createPointsAroundCenter($aMatch[1], $aMatch[2], $fRadius);
+         //
+     }
+ 
+     if (isset($aPolyPoints)) {
+         $aResultPoints = array();
+         foreach ($aPolyPoints as $aPoint) {
+             $aResultPoints[] = array($aPoint[1], $aPoint[2]);
+         }
+         return $aResultPoints;
+     }
+ 
+     return;
+ }
+ 
+ function createPointsAroundCenter($fLon, $fLat, $fRadius)
+ {
+     $iSteps = max(8, min(100, ($fRadius * 40000)^2));
+     $fStepSize = (2*pi())/$iSteps;
+     $aPolyPoints = array();
+     for ($f = 0; $f < 2*pi(); $f += $fStepSize) {
+         $aPolyPoints[] = array('', $fLon+($fRadius*sin($f)), $fLat+($fRadius*cos($f)) );
+     }
+     return $aPolyPoints;
+ }
diff --cc utils/update.php
index a05ad9e4,95e99b9f..efdf6525
--- a/utils/update.php
+++ b/utils/update.php
@@@ -1,385 -1,335 +1,344 @@@
  #!/usr/bin/php -Cq
   getTotalMemoryMB())
- 	{
- 		$iCacheMemory = getCacheMemoryMB();
- 		echo "WARNING: resetting cache memory to $iCacheMemory\n";
- 	}
- 	$sOsm2pgsqlCmd = CONST_Osm2pgsql_Binary.' -klas --number-processes 1 -C '.$iCacheMemory.' -O gazetteer -d '.$aDSNInfo['database'].' -P '.$aDSNInfo['port'];
- 	if (!is_null(CONST_Osm2pgsql_Flatnode_File))
- 	{
- 		$sOsm2pgsqlCmd .= ' --flat-nodes '.CONST_Osm2pgsql_Flatnode_File;
- 	}
- 
- 
- 	if (isset($aResult['import-diff']))
- 	{
- 		// import diff directly (e.g. from osmosis --rri)
- 		$sNextFile = $aResult['import-diff'];
- 		if (!file_exists($sNextFile))
- 		{
- 			fail("Cannot open $sNextFile\n");
- 		}
- 
- 		// Import the file
- 		$sCMD = $sOsm2pgsqlCmd.' '.$sNextFile;
- 		echo $sCMD."\n";
- 		exec($sCMD, $sJunk, $iErrorLevel);
- 
- 		if ($iErrorLevel)
- 		{
- 			fail("Error from osm2pgsql, $iErrorLevel\n");
- 		}
- 
- 		// Don't update the import status - we don't know what this file contains
- 	}
- 
- 	$sTemporaryFile = CONST_BasePath.'/data/osmosischange.osc';
- 	$bHaveDiff = false;
- 	if (isset($aResult['import-file']) && $aResult['import-file'])
- 	{
- 		$bHaveDiff = true;
- 		$sCMD = CONST_Osmosis_Binary.' --read-xml \''.$aResult['import-file'].'\' --read-empty --derive-change --write-xml-change '.$sTemporaryFile;
- 		echo $sCMD."\n";
- 		exec($sCMD, $sJunk, $iErrorLevel);
- 		if ($iErrorLevel)
- 		{
- 			fail("Error converting osm to osc, osmosis returned: $iErrorLevel\n");
- 		}
- 	}
- 
- 	$bUseOSMApi = isset($aResult['import-from-main-api']) && $aResult['import-from-main-api'];
- 	$sContentURL = '';
- 	if (isset($aResult['import-node']) && $aResult['import-node'])
- 	{
- 		if ($bUseOSMApi)
- 		{
- 			$sContentURL = 'http://www.openstreetmap.org/api/0.6/node/'.$aResult['import-node'];
- 		}
- 		else
- 		{
- 			$sContentURL = 'http://overpass-api.de/api/interpreter?data=node('.$aResult['import-node'].');out%20meta;';
- 		}
- 	}
- 	if (isset($aResult['import-way']) && $aResult['import-way'])
- 	{
- 		if ($bUseOSMApi)
- 		{
- 			$sContentURL = 'http://www.openstreetmap.org/api/0.6/way/'.$aResult['import-way'].'/full';
- 		}
- 		else
- 		{
- 			$sContentURL = 'http://overpass-api.de/api/interpreter?data=(way('.$aResult['import-way'].');node(w););out%20meta;';
- 		}
- 	}
- 	if (isset($aResult['import-relation']) && $aResult['import-relation'])
- 	{
- 		if ($bUseOSMApi)
- 		{
- 			$sContentURLsModifyXMLstr = 'http://www.openstreetmap.org/api/0.6/relation/'.$aResult['import-relation'].'/full';
- 		}
- 		else
- 		{
- 			$sContentURL = 'http://overpass-api.de/api/interpreter?data=((rel('.$aResult['import-relation'].');way(r);node(w));node(r));out%20meta;';
- 		}
- 	}
- 	if ($sContentURL)
- 	{
- 		$sModifyXMLstr = file_get_contents($sContentURL);
- 		$bHaveDiff = true;
- 
- 		$aSpec = array(
- 			0 => array("pipe", "r"),  // stdin
- 			1 => array("pipe", "w"),  // stdout
- 			2 => array("pipe", "w") // stderr
- 		);
- 		$sCMD = CONST_Osmosis_Binary.' --read-xml - --read-empty --derive-change --write-xml-change '.$sTemporaryFile;
- 		echo $sCMD."\n";
- 		$hProc = proc_open($sCMD, $aSpec, $aPipes);
- 		if (!is_resource($hProc))
- 		{
- 			fail("Error converting osm to osc, osmosis failed\n");
- 		}
- 		fwrite($aPipes[0], $sModifyXMLstr);
- 		fclose($aPipes[0]);
- 		$sOut = stream_get_contents($aPipes[1]);
- 		if ($aResult['verbose']) echo $sOut;
- 		fclose($aPipes[1]);
- 		$sErrors = stream_get_contents($aPipes[2]);
- 		if ($aResult['verbose']) echo $sErrors;
- 		fclose($aPipes[2]);
- 		if ($iError = proc_close($hProc))
- 		{
- 			echo $sOut;
- 			echo $sErrors;
- 			fail("Error converting osm to osc, osmosis returned: $iError\n");
- 		}
- 	}
- 
- 	if ($bHaveDiff)
- 	{
- 		// import generated change file
- 		$sCMD = $sOsm2pgsqlCmd.' '.$sTemporaryFile;
- 		echo $sCMD."\n";
- 		exec($sCMD, $sJunk, $iErrorLevel);
- 		if ($iErrorLevel)
- 		{
- 			fail("osm2pgsql exited with error level $iErrorLevel\n");
- 		}
- 	}
- 
- 	if ($aResult['deduplicate'])
- 	{
- 
- 		if (getPostgresVersion() < 9.3)
- 		{
- 			fail("ERROR: deduplicate is only currently supported in postgresql 9.3");
- 		}
- 
- 		$oDB =& getDB();
- 		$sSQL = 'select partition from country_name order by country_code';
- 		$aPartitions = chksql($oDB->getCol($sSQL));
- 		$aPartitions[] = 0;
- 
- 		$sSQL = "select word_token,count(*) from word where substr(word_token, 1, 1) = ' ' and class is null and type is null and country_code is null group by word_token having count(*) > 1 order by word_token";
- 		$aDuplicateTokens = chksql($oDB->getAll($sSQL));
- 		foreach($aDuplicateTokens as $aToken)
- 		{
- 			if (trim($aToken['word_token']) == '' || trim($aToken['word_token']) == '-') continue;
- 			echo "Deduping ".$aToken['word_token']."\n";
- 			$sSQL = "select word_id,(select count(*) from search_name where nameaddress_vector @> ARRAY[word_id]) as num from word where word_token = '".$aToken['word_token']."' and class is null and type is null and country_code is null order by num desc";
- 			$aTokenSet = chksql($oDB->getAll($sSQL));
- 
- 			$aKeep = array_shift($aTokenSet);
- 			$iKeepID = $aKeep['word_id'];
- 
- 			foreach($aTokenSet as $aRemove)
- 			{
- 				$sSQL = "update search_name set";
- 				$sSQL .= " name_vector = array_replace(name_vector,".$aRemove['word_id'].",".$iKeepID."),";
- 				$sSQL .= " nameaddress_vector = array_replace(nameaddress_vector,".$aRemove['word_id'].",".$iKeepID.")";
- 				$sSQL .= " where name_vector @> ARRAY[".$aRemove['word_id']."]";
- 				chksql($oDB->query($sSQL));
- 
- 				$sSQL = "update search_name set";
- 				$sSQL .= " nameaddress_vector = array_replace(nameaddress_vector,".$aRemove['word_id'].",".$iKeepID.")";
- 				$sSQL .= " where nameaddress_vector @> ARRAY[".$aRemove['word_id']."]";
- 				chksql($oDB->query($sSQL));
- 
- 				$sSQL = "update location_area_country set";
- 				$sSQL .= " keywords = array_replace(keywords,".$aRemove['word_id'].",".$iKeepID.")";
- 				$sSQL .= " where keywords @> ARRAY[".$aRemove['word_id']."]";
- 				chksql($oDB->query($sSQL));
- 
- 				foreach ($aPartitions as $sPartition)
- 				{
- 					$sSQL = "update search_name_".$sPartition." set";
- 					$sSQL .= " name_vector = array_replace(name_vector,".$aRemove['word_id'].",".$iKeepID.")";
- 					$sSQL .= " where name_vector @> ARRAY[".$aRemove['word_id']."]";
- 					chksql($oDB->query($sSQL));
- 
- 					$sSQL = "update location_area_country set";
- 					$sSQL .= " keywords = array_replace(keywords,".$aRemove['word_id'].",".$iKeepID.")";
- 					$sSQL .= " where keywords @> ARRAY[".$aRemove['word_id']."]";
- 					chksql($oDB->query($sSQL));
- 				}
- 
- 				$sSQL = "delete from word where word_id = ".$aRemove['word_id'];
- 				chksql($oDB->query($sSQL));
- 			}
- 		}
- 	}
- 
- 	if ($aResult['index'])
- 	{
- 		passthru(CONST_InstallPath.'/nominatim/nominatim -i -d '.$aDSNInfo['database'].' -P '.$aDSNInfo['port'].' -t '.$aResult['index-instances'].' -r '.$aResult['index-rank']);
- 	}
- 
- 	if ($aResult['import-osmosis'] || $aResult['import-osmosis-all'])
- 	{
- 
- 		if (strpos(CONST_Replication_Url, 'download.geofabrik.de') !== false && CONST_Replication_Update_Interval < 86400) {
- 			fail("Error: Update interval too low for download.geofabrik.de.  Please check install documentation (http://wiki.openstreetmap.org/wiki/Nominatim/Installation#Updates)\n");
- 		}
- 
- 		$sImportFile = CONST_BasePath.'/data/osmosischange.osc';
- 		$sOsmosisConfigDirectory = CONST_InstallPath.'/settings';
- 		$sCMDDownload = CONST_Osmosis_Binary.' --read-replication-interval workingDirectory='.$sOsmosisConfigDirectory.' --simplify-change --write-xml-change '.$sImportFile;
- 		$sCMDCheckReplicationLag = CONST_Osmosis_Binary.' -q --read-replication-lag workingDirectory='.$sOsmosisConfigDirectory;
- 		$sCMDImport = $sOsm2pgsqlCmd.' '.$sImportFile;
- 		$sCMDIndex = CONST_InstallPath.'/nominatim/nominatim -i -d '.$aDSNInfo['database'].' -P '.$aDSNInfo['port'].' -t '.$aResult['index-instances'];
- 
- 		while(true)
- 		{
- 			$fStartTime = time();
- 			$iFileSize = 1001;
- 
- 			if (!file_exists($sImportFile))
- 			{
- 				// First check if there are new updates published (except for minutelies - there's always new diffs to process)
- 				if ( CONST_Replication_Update_Interval > 60 )
- 				{
- 
- 					unset($aReplicationLag);
- 					exec($sCMDCheckReplicationLag, $aReplicationLag, $iErrorLevel); 
- 					while ($iErrorLevel > 0 || $aReplicationLag[0] < 1)
- 					{
- 						if ($iErrorLevel)
- 						{
- 							echo "Error: $iErrorLevel. ";
- 							echo "Re-trying: ".$sCMDCheckReplicationLag." in ".CONST_Replication_Recheck_Interval." secs\n";
- 						}
- 						else
- 						{
- 							echo ".";
- 						}
- 						sleep(CONST_Replication_Recheck_Interval);
- 						unset($aReplicationLag);
- 						exec($sCMDCheckReplicationLag, $aReplicationLag, $iErrorLevel); 
- 					}
- 					// There are new replication files - use osmosis to download the file
- 					echo "\n".date('Y-m-d H:i:s')." Replication Delay is ".$aReplicationLag[0]."\n";
- 				}
- 				$fStartTime = time();
- 				$fCMDStartTime = time();
- 				echo $sCMDDownload."\n";
- 				exec($sCMDDownload, $sJunk, $iErrorLevel);
- 				while ($iErrorLevel > 0)
- 				{
- 					echo "Error: $iErrorLevel\n";
- 					sleep(60);
- 					echo 'Re-trying: '.$sCMDDownload."\n";
- 					exec($sCMDDownload, $sJunk, $iErrorLevel);
- 				}
- 				$iFileSize = filesize($sImportFile);
- 				$sBatchEnd = getosmosistimestamp($sOsmosisConfigDirectory);
- 				$sSQL = "INSERT INTO import_osmosis_log values ('$sBatchEnd',$iFileSize,'".date('Y-m-d H:i:s',$fCMDStartTime)."','".date('Y-m-d H:i:s')."','osmosis')";
- 				var_Dump($sSQL);
- 				$oDB->query($sSQL);
- 				echo date('Y-m-d H:i:s')." Completed osmosis step for $sBatchEnd in ".round((time()-$fCMDStartTime)/60,2)." minutes\n";
- 			}
- 
- 			$iFileSize = filesize($sImportFile);
- 			$sBatchEnd = getosmosistimestamp($sOsmosisConfigDirectory);
- 	
- 			// Import the file
- 			$fCMDStartTime = time();
- 			echo $sCMDImport."\n";
- 			exec($sCMDImport, $sJunk, $iErrorLevel);
- 			if ($iErrorLevel)
- 			{
- 				echo "Error: $iErrorLevel\n";
- 				exit($iErrorLevel);
- 			}
- 			$sSQL = "INSERT INTO import_osmosis_log values ('$sBatchEnd',$iFileSize,'".date('Y-m-d H:i:s',$fCMDStartTime)."','".date('Y-m-d H:i:s')."','osm2pgsql')";
- 			var_Dump($sSQL);
- 			$oDB->query($sSQL);
- 			echo date('Y-m-d H:i:s')." Completed osm2pgsql step for $sBatchEnd in ".round((time()-$fCMDStartTime)/60,2)." minutes\n";
- 
- 			// Archive for debug?
- 			unlink($sImportFile);
- 
- 			$sBatchEnd = getosmosistimestamp($sOsmosisConfigDirectory);
- 
- 			// Index file
- 			if (!isset($aResult['index-instances']))
- 			{
- 				if (getLoadAverage() < 24)
- 					$iIndexInstances = 2;
- 				else
- 					$iIndexInstances = 1;
- 			} else
- 				$iIndexInstances = $aResult['index-instances'];
- 
- 			$sThisIndexCmd = $sCMDIndex.' -t '.$iIndexInstances;
- 			$fCMDStartTime = time();
- 
- 			if (!$aResult['no-index'])
- 			{
- 				echo "$sThisIndexCmd\n";
- 				exec($sThisIndexCmd, $sJunk, $iErrorLevel);
- 				if ($iErrorLevel)
- 				{
- 					echo "Error: $iErrorLevel\n";
- 					exit($iErrorLevel);
- 				}
- 			}
- 
- 			$sSQL = "INSERT INTO import_osmosis_log values ('$sBatchEnd',$iFileSize,'".date('Y-m-d H:i:s',$fCMDStartTime)."','".date('Y-m-d H:i:s')."','index')";
- 			var_Dump($sSQL);
- 			$oDB->query($sSQL);
- 			echo date('Y-m-d H:i:s')." Completed index step for $sBatchEnd in ".round((time()-$fCMDStartTime)/60,2)." minutes\n";
- 
- 			$sSQL = "update import_status set lastimportdate = '$sBatchEnd'";
- 			$oDB->query($sSQL);
- 
- 			$fDuration = time() - $fStartTime;
- 			echo date('Y-m-d H:i:s')." Completed all for $sBatchEnd in ".round($fDuration/60,2)." minutes\n";
- 			if (!$aResult['import-osmosis-all']) exit(0);
- 
- 			if ( CONST_Replication_Update_Interval > 60 )
- 			{
- 				$iSleep = max(0,(strtotime($sBatchEnd)+CONST_Replication_Update_Interval-time()));
- 			}
- 			else
- 			{
- 				$iSleep = max(0,CONST_Replication_Update_Interval-$fDuration);
- 			}
- 			echo date('Y-m-d H:i:s')." Sleeping $iSleep seconds\n";
- 			sleep($iSleep);
- 		}
- 	}
- 
- 	function getosmosistimestamp($sOsmosisConfigDirectory)
- 	{
- 		$sStateFile = file_get_contents($sOsmosisConfigDirectory.'/state.txt');
- 		preg_match('#timestamp=(.+)#', $sStateFile, $aResult);
- 		return str_replace('\:',':',$aResult[1]);
- 	}
+ require_once(dirname(dirname(__FILE__)).'/settings/settings.php');
+ require_once(CONST_BasePath.'/lib/init-cmd.php');
+ ini_set('memory_limit', '800M');
+ 
+ $aCMDOptions
+ = array(
+    "Import / update / index osm data",
+    array('help', 'h', 0, 1, 0, 0, false, 'Show Help'),
+    array('quiet', 'q', 0, 1, 0, 0, 'bool', 'Quiet output'),
+    array('verbose', 'v', 0, 1, 0, 0, 'bool', 'Verbose output'),
+ 
+    array('import-osmosis', '', 0, 1, 0, 0, 'bool', 'Import using osmosis'),
+    array('import-osmosis-all', '', 0, 1, 0, 0, 'bool', 'Import using osmosis forever'),
+    array('no-npi', '', 0, 1, 0, 0, 'bool', '(obsolate)'),
+    array('no-index', '', 0, 1, 0, 0, 'bool', 'Do not index the new data'),
+ 
+    array('import-all', '', 0, 1, 0, 0, 'bool', 'Import all available files'),
+ 
+    array('import-file', '', 0, 1, 1, 1, 'realpath', 'Re-import data from an OSM file'),
+    array('import-diff', '', 0, 1, 1, 1, 'realpath', 'Import a diff (osc) file from local file system'),
+    array('osm2pgsql-cache', '', 0, 1, 1, 1, 'int', 'Cache size used by osm2pgsql'),
+ 
+    array('import-node', '', 0, 1, 1, 1, 'int', 'Re-import node'),
+    array('import-way', '', 0, 1, 1, 1, 'int', 'Re-import way'),
+    array('import-relation', '', 0, 1, 1, 1, 'int', 'Re-import relation'),
+    array('import-from-main-api', '', 0, 1, 0, 0, 'bool', 'Use OSM API instead of Overpass to download objects'),
+ 
+    array('index', '', 0, 1, 0, 0, 'bool', 'Index'),
+    array('index-rank', '', 0, 1, 1, 1, 'int', 'Rank to start indexing from'),
+    array('index-instances', '', 0, 1, 1, 1, 'int', 'Number of indexing instances (threads)'),
+ 
+    array('deduplicate', '', 0, 1, 0, 0, 'bool', 'Deduplicate tokens'),
+   );
+ getCmdOpt($_SERVER['argv'], $aCMDOptions, $aResult, true, true);
+ 
+ if (!isset($aResult['index-instances'])) $aResult['index-instances'] = 1;
+ if (!isset($aResult['index-rank'])) $aResult['index-rank'] = 0;
+ 
+ date_default_timezone_set('Etc/UTC');
+ 
+ $oDB =& getDB();
+ 
+ $aDSNInfo = DB::parseDSN(CONST_Database_DSN);
+ if (!isset($aDSNInfo['port']) || !$aDSNInfo['port']) $aDSNInfo['port'] = 5432;
+ 
+ // cache memory to be used by osm2pgsql, should not be more than the available memory
+ $iCacheMemory = (isset($aResult['osm2pgsql-cache'])?$aResult['osm2pgsql-cache']:2000);
+ if ($iCacheMemory + 500 > getTotalMemoryMB()) {
+     $iCacheMemory = getCacheMemoryMB();
+     echo "WARNING: resetting cache memory to $iCacheMemory\n";
+ }
+ $sOsm2pgsqlCmd = CONST_Osm2pgsql_Binary.' -klas --number-processes 1 -C '.$iCacheMemory.' -O gazetteer -d '.$aDSNInfo['database'].' -P '.$aDSNInfo['port'];
+ if (!is_null(CONST_Osm2pgsql_Flatnode_File)) {
+     $sOsm2pgsqlCmd .= ' --flat-nodes '.CONST_Osm2pgsql_Flatnode_File;
+ }
+ 
+ 
+ if (isset($aResult['import-diff'])) {
+     // import diff directly (e.g. from osmosis --rri)
+     $sNextFile = $aResult['import-diff'];
+     if (!file_exists($sNextFile)) {
+         fail("Cannot open $sNextFile\n");
+     }
+ 
+     // Import the file
+     $sCMD = $sOsm2pgsqlCmd.' '.$sNextFile;
+     echo $sCMD."\n";
+     exec($sCMD, $sJunk, $iErrorLevel);
+ 
+     if ($iErrorLevel) {
+         fail("Error from osm2pgsql, $iErrorLevel\n");
+     }
+ 
+     // Don't update the import status - we don't know what this file contains
+ }
+ 
+ $sTemporaryFile = CONST_BasePath.'/data/osmosischange.osc';
+ $bHaveDiff = false;
+ if (isset($aResult['import-file']) && $aResult['import-file']) {
+     $bHaveDiff = true;
+     $sCMD = CONST_Osmosis_Binary.' --read-xml \''.$aResult['import-file'].'\' --read-empty --derive-change --write-xml-change '.$sTemporaryFile;
+     echo $sCMD."\n";
+     exec($sCMD, $sJunk, $iErrorLevel);
+     if ($iErrorLevel) {
+         fail("Error converting osm to osc, osmosis returned: $iErrorLevel\n");
+     }
+ }
+ 
+ $bUseOSMApi = isset($aResult['import-from-main-api']) && $aResult['import-from-main-api'];
+ $sContentURL = '';
+ if (isset($aResult['import-node']) && $aResult['import-node']) {
+     if ($bUseOSMApi) {
+         $sContentURL = 'http://www.openstreetmap.org/api/0.6/node/'.$aResult['import-node'];
+     } else {
+         $sContentURL = 'http://overpass-api.de/api/interpreter?data=node('.$aResult['import-node'].');out%20meta;';
+     }
+ }
+ 
+ if (isset($aResult['import-way']) && $aResult['import-way']) {
+     if ($bUseOSMApi) {
+         $sContentURL = 'http://www.openstreetmap.org/api/0.6/way/'.$aResult['import-way'].'/full';
+     } else {
+         $sContentURL = 'http://overpass-api.de/api/interpreter?data=(way('.$aResult['import-way'].');node(w););out%20meta;';
+     }
+ }
+ 
+ if (isset($aResult['import-relation']) && $aResult['import-relation']) {
+     if ($bUseOSMApi) {
+         $sContentURLsModifyXMLstr = 'http://www.openstreetmap.org/api/0.6/relation/'.$aResult['import-relation'].'/full';
+     } else {
+         $sContentURL = 'http://overpass-api.de/api/interpreter?data=((rel('.$aResult['import-relation'].');way(r);node(w));node(r));out%20meta;';
+     }
+ }
+ 
+ if ($sContentURL) {
+     $sModifyXMLstr = file_get_contents($sContentURL);
+     $bHaveDiff = true;
+ 
+     $aSpec = array(
+               0 => array("pipe", "r"),  // stdin
+               1 => array("pipe", "w"),  // stdout
+               2 => array("pipe", "w") // stderr
+              );
+     $sCMD = CONST_Osmosis_Binary.' --read-xml - --read-empty --derive-change --write-xml-change '.$sTemporaryFile;
+     echo $sCMD."\n";
+     $hProc = proc_open($sCMD, $aSpec, $aPipes);
+     if (!is_resource($hProc)) {
+         fail("Error converting osm to osc, osmosis failed\n");
+     }
+     fwrite($aPipes[0], $sModifyXMLstr);
+     fclose($aPipes[0]);
+     $sOut = stream_get_contents($aPipes[1]);
+     if ($aResult['verbose']) echo $sOut;
+     fclose($aPipes[1]);
+     $sErrors = stream_get_contents($aPipes[2]);
+     if ($aResult['verbose']) echo $sErrors;
+     fclose($aPipes[2]);
+     if ($iError = proc_close($hProc)) {
+         echo $sOut;
+         echo $sErrors;
+         fail("Error converting osm to osc, osmosis returned: $iError\n");
+     }
+ }
+ 
+ if ($bHaveDiff) {
+     // import generated change file
+     $sCMD = $sOsm2pgsqlCmd.' '.$sTemporaryFile;
+     echo $sCMD."\n";
+     exec($sCMD, $sJunk, $iErrorLevel);
+     if ($iErrorLevel) {
+         fail("osm2pgsql exited with error level $iErrorLevel\n");
+     }
+ }
+ 
+ if ($aResult['deduplicate']) {
+     //
+     if (getPostgresVersion() < 9.3) {
+         fail("ERROR: deduplicate is only currently supported in postgresql 9.3");
+     }
+ 
+     $oDB =& getDB();
+     $sSQL = 'select partition from country_name order by country_code';
+     $aPartitions = chksql($oDB->getCol($sSQL));
+     $aPartitions[] = 0;
+ 
+     $sSQL = "select word_token,count(*) from word where substr(word_token, 1, 1) = ' ' and class is null and type is null and country_code is null group by word_token having count(*) > 1 order by word_token";
+     $aDuplicateTokens = chksql($oDB->getAll($sSQL));
+     foreach ($aDuplicateTokens as $aToken) {
+         if (trim($aToken['word_token']) == '' || trim($aToken['word_token']) == '-') continue;
+         echo "Deduping ".$aToken['word_token']."\n";
+         $sSQL = "select word_id,(select count(*) from search_name where nameaddress_vector @> ARRAY[word_id]) as num from word where word_token = '".$aToken['word_token']."' and class is null and type is null and country_code is null order by num desc";
+         $aTokenSet = chksql($oDB->getAll($sSQL));
+ 
+         $aKeep = array_shift($aTokenSet);
+         $iKeepID = $aKeep['word_id'];
+ 
+         foreach ($aTokenSet as $aRemove) {
+             $sSQL = "update search_name set";
+             $sSQL .= " name_vector = array_replace(name_vector,".$aRemove['word_id'].",".$iKeepID."),";
+             $sSQL .= " nameaddress_vector = array_replace(nameaddress_vector,".$aRemove['word_id'].",".$iKeepID.")";
+             $sSQL .= " where name_vector @> ARRAY[".$aRemove['word_id']."]";
+             chksql($oDB->query($sSQL));
+ 
+             $sSQL = "update search_name set";
+             $sSQL .= " nameaddress_vector = array_replace(nameaddress_vector,".$aRemove['word_id'].",".$iKeepID.")";
+             $sSQL .= " where nameaddress_vector @> ARRAY[".$aRemove['word_id']."]";
+             chksql($oDB->query($sSQL));
+ 
+             $sSQL = "update location_area_country set";
+             $sSQL .= " keywords = array_replace(keywords,".$aRemove['word_id'].",".$iKeepID.")";
+             $sSQL .= " where keywords @> ARRAY[".$aRemove['word_id']."]";
+             chksql($oDB->query($sSQL));
+ 
+             foreach ($aPartitions as $sPartition) {
+                 $sSQL = "update search_name_".$sPartition." set";
+                 $sSQL .= " name_vector = array_replace(name_vector,".$aRemove['word_id'].",".$iKeepID.")";
+                 $sSQL .= " where name_vector @> ARRAY[".$aRemove['word_id']."]";
+                 chksql($oDB->query($sSQL));
+ 
+                 $sSQL = "update location_area_country set";
+                 $sSQL .= " keywords = array_replace(keywords,".$aRemove['word_id'].",".$iKeepID.")";
+                 $sSQL .= " where keywords @> ARRAY[".$aRemove['word_id']."]";
+                 chksql($oDB->query($sSQL));
+             }
+ 
+             $sSQL = "delete from word where word_id = ".$aRemove['word_id'];
+             chksql($oDB->query($sSQL));
+         }
+     }
+ }
+ 
+ if ($aResult['index']) {
+     passthru(CONST_InstallPath.'/nominatim/nominatim -i -d '.$aDSNInfo['database'].' -P '.$aDSNInfo['port'].' -t '.$aResult['index-instances'].' -r '.$aResult['index-rank']);
+ }
+ 
+ if ($aResult['import-osmosis'] || $aResult['import-osmosis-all']) {
+     //
+     if (strpos(CONST_Replication_Url, 'download.geofabrik.de') !== false && CONST_Replication_Update_Interval < 86400) {
+         fail("Error: Update interval too low for download.geofabrik.de.  Please check install documentation (http://wiki.openstreetmap.org/wiki/Nominatim/Installation#Updates)\n");
+     }
+ 
+     $sImportFile = CONST_BasePath.'/data/osmosischange.osc';
+     $sOsmosisConfigDirectory = CONST_InstallPath.'/settings';
+     $sCMDDownload = CONST_Osmosis_Binary.' --read-replication-interval workingDirectory='.$sOsmosisConfigDirectory.' --simplify-change --write-xml-change '.$sImportFile;
+     $sCMDCheckReplicationLag = CONST_Osmosis_Binary.' -q --read-replication-lag workingDirectory='.$sOsmosisConfigDirectory;
+     $sCMDImport = $sOsm2pgsqlCmd.' '.$sImportFile;
+     $sCMDIndex = CONST_InstallPath.'/nominatim/nominatim -i -d '.$aDSNInfo['database'].' -P '.$aDSNInfo['port'].' -t '.$aResult['index-instances'];
+ 
+     while (true) {
+         $fStartTime = time();
+         $iFileSize = 1001;
+ 
+         if (!file_exists($sImportFile)) {
+             // First check if there are new updates published (except for minutelies - there's always new diffs to process)
+             if (CONST_Replication_Update_Interval > 60) {
+                 unset($aReplicationLag);
+                 exec($sCMDCheckReplicationLag, $aReplicationLag, $iErrorLevel);
+                 while ($iErrorLevel > 0 || $aReplicationLag[0] < 1) {
+                     if ($iErrorLevel) {
+                         echo "Error: $iErrorLevel. ";
+                         echo "Re-trying: ".$sCMDCheckReplicationLag." in ".CONST_Replication_Recheck_Interval." secs\n";
+                     } else {
+                         echo ".";
+                     }
+                     sleep(CONST_Replication_Recheck_Interval);
+                     unset($aReplicationLag);
+                     exec($sCMDCheckReplicationLag, $aReplicationLag, $iErrorLevel);
+                 }
+                 // There are new replication files - use osmosis to download the file
+                 echo "\n".date('Y-m-d H:i:s')." Replication Delay is ".$aReplicationLag[0]."\n";
+             }
+             $fStartTime = time();
+             $fCMDStartTime = time();
+             echo $sCMDDownload."\n";
+             exec($sCMDDownload, $sJunk, $iErrorLevel);
+             while ($iErrorLevel > 0) {
+                 echo "Error: $iErrorLevel\n";
+                 sleep(60);
+                 echo 'Re-trying: '.$sCMDDownload."\n";
+                 exec($sCMDDownload, $sJunk, $iErrorLevel);
+             }
+             $iFileSize = filesize($sImportFile);
+             $sBatchEnd = getosmosistimestamp($sOsmosisConfigDirectory);
+             $sSQL = "INSERT INTO import_osmosis_log values ('$sBatchEnd',$iFileSize,'".date('Y-m-d H:i:s', $fCMDStartTime)."','".date('Y-m-d H:i:s')."','osmosis')";
+             var_Dump($sSQL);
+             $oDB->query($sSQL);
+             echo date('Y-m-d H:i:s')." Completed osmosis step for $sBatchEnd in ".round((time()-$fCMDStartTime)/60, 2)." minutes\n";
+         }
+ 
+         $iFileSize = filesize($sImportFile);
+         $sBatchEnd = getosmosistimestamp($sOsmosisConfigDirectory);
+ 
+         // Import the file
+         $fCMDStartTime = time();
+         echo $sCMDImport."\n";
+         exec($sCMDImport, $sJunk, $iErrorLevel);
+         if ($iErrorLevel) {
+             echo "Error: $iErrorLevel\n";
+             exit($iErrorLevel);
+         }
+         $sSQL = "INSERT INTO import_osmosis_log values ('$sBatchEnd',$iFileSize,'".date('Y-m-d H:i:s', $fCMDStartTime)."','".date('Y-m-d H:i:s')."','osm2pgsql')";
+         var_Dump($sSQL);
+         $oDB->query($sSQL);
+         echo date('Y-m-d H:i:s')." Completed osm2pgsql step for $sBatchEnd in ".round((time()-$fCMDStartTime)/60, 2)." minutes\n";
+ 
+         // Archive for debug?
+         unlink($sImportFile);
+ 
+         $sBatchEnd = getosmosistimestamp($sOsmosisConfigDirectory);
+ 
+         // Index file
+         $sThisIndexCmd = $sCMDIndex;
++        if (!isset($aResult['index-instances'])) {
++            if (getLoadAverage() < 24)
++                $iIndexInstances = 2;
++            else
++                $iIndexInstances = 1;
++        } else
++            $iIndexInstances = $aResult['index-instances'];
++
++        $sThisIndexCmd = $sCMDIndex.' -t '.$iIndexInstances;
+         $fCMDStartTime = time();
+ 
+         if (!$aResult['no-index']) {
+             echo "$sThisIndexCmd\n";
+             exec($sThisIndexCmd, $sJunk, $iErrorLevel);
+             if ($iErrorLevel) {
+                 echo "Error: $iErrorLevel\n";
+                 exit($iErrorLevel);
+             }
+         }
+ 
+         $sSQL = "INSERT INTO import_osmosis_log values ('$sBatchEnd',$iFileSize,'".date('Y-m-d H:i:s', $fCMDStartTime)."','".date('Y-m-d H:i:s')."','index')";
+         var_Dump($sSQL);
+         $oDB->query($sSQL);
+         echo date('Y-m-d H:i:s')." Completed index step for $sBatchEnd in ".round((time()-$fCMDStartTime)/60, 2)." minutes\n";
+ 
+         $sSQL = "update import_status set lastimportdate = '$sBatchEnd'";
+         $oDB->query($sSQL);
+ 
+         $fDuration = time() - $fStartTime;
+         echo date('Y-m-d H:i:s')." Completed all for $sBatchEnd in ".round($fDuration/60, 2)." minutes\n";
+         if (!$aResult['import-osmosis-all']) exit(0);
+ 
+         if (CONST_Replication_Update_Interval > 60) {
+             $iSleep = max(0, (strtotime($sBatchEnd)+CONST_Replication_Update_Interval-time()));
+         } else {
+             $iSleep = max(0, CONST_Replication_Update_Interval-$fDuration);
+         }
+         echo date('Y-m-d H:i:s')." Sleeping $iSleep seconds\n";
+         sleep($iSleep);
+     }
+ }
+ 
+ 
+ function getosmosistimestamp($sOsmosisConfigDirectory)
+ {
+     $sStateFile = file_get_contents($sOsmosisConfigDirectory.'/state.txt');
+     preg_match('#timestamp=(.+)#', $sStateFile, $aResult);
+     return str_replace('\:', ':', $aResult[1]);
+ }
diff --cc website/reverse.php
index f3763866,bd852e32..9c9b99fb
--- a/website/reverse.php
+++ b/website/reverse.php
@@@ -1,110 -1,102 +1,104 @@@
   CONST_PolygonOutput_MaximumTypes)
- 	{
- 		if (CONST_PolygonOutput_MaximumTypes)
- 		{
- 			userError("Select only ".CONST_PolygonOutput_MaximumTypes." polgyon output option");
- 		}
- 		else
- 		{
- 			userError("Polygon output is disabled");
- 		}
- 		exit;
- 	}
- 
- 
- 	// Polygon simplification threshold (optional)
- 	$fThreshold = getParamFloat('polygon_threshold', 0.0);
- 
- 
- 	$oDB =& getDB();
- 	ini_set('memory_limit', '200M');
- 
- 	// Format for output
- 	$sOutputFormat = getParamSet('format', array('html', 'xml', 'json', 'jsonv2'), 'xml');
- 
- 	// Preferred language
- 	$aLangPrefOrder = getPreferredLanguages();
- 
- 	$hLog = logStart($oDB, 'reverse', $_SERVER['QUERY_STRING'], $aLangPrefOrder);
- 
- 
- 	$oPlaceLookup = new PlaceLookup($oDB);
- 	$oPlaceLookup->setLanguagePreference($aLangPrefOrder);
- 	$oPlaceLookup->setIncludeAddressDetails(getParamBool('addressdetails', true));
- 	$oPlaceLookup->setIncludeExtraTags(getParamBool('extratags', false));
- 	$oPlaceLookup->setIncludeNameDetails(getParamBool('namedetails', false));
- 
- 	$sOsmType = getParamSet('osm_type', array('N', 'W', 'R'));
- 	$iOsmId = getParamInt('osm_id', -1);
- 	$fLat = getParamFloat('lat');
- 	$fLon = getParamFloat('lon');
- 	if ($sOsmType && $iOsmId > 0)
- 	{
- 		$aPlace = $oPlaceLookup->lookupOSMID($sOsmType, $iOsmId);
- 	}
- 	else if ($fLat !== false && $fLon !== false)
- 	{
- 		$oReverseGeocode = new ReverseGeocode($oDB);
- 		$oReverseGeocode->setZoom(getParamInt('zoom', 18));
- 
- 		$aLookup = $oReverseGeocode->lookup($fLat, $fLon);
- 		if (CONST_Debug) var_dump($aLookup);
- 
- 		$aPlace = $oPlaceLookup->lookup((int)$aLookup['place_id'],
- 		                                $aLookup['type'], $aLookup['fraction']);
- 	}
- 	else if ($sOutputFormat != 'html')
- 	{
- 		userError("Need coordinates or OSM object to lookup.");
- 	}
- 
- 	if ($aPlace)
- 	{
- 		$oPlaceLookup->setIncludePolygonAsPoints(false);
- 		$oPlaceLookup->setIncludePolygonAsText($bAsText);
- 		$oPlaceLookup->setIncludePolygonAsGeoJSON($bAsGeoJSON);
- 		$oPlaceLookup->setIncludePolygonAsKML($bAsKML);
- 		$oPlaceLookup->setIncludePolygonAsSVG($bAsSVG);
- 		$oPlaceLookup->setPolygonSimplificationThreshold($fThreshold);
- 
- 		$fRadius = $fDiameter = getResultDiameter($aPlace);
- 		$aOutlineResult = $oPlaceLookup->getOutlines($aPlace['place_id'],
- 		                                             $aPlace['lon'], $aPlace['lat'],
- 		                                             $fRadius);
- 
- 		if ($aOutlineResult)
- 		{
- 			$aPlace = array_merge($aPlace, $aOutlineResult);
- 		}
- 	}
- 
- 	logEnd($oDB, $hLog, sizeof($aPlace)?1:0);
- 
- 	if (CONST_Debug)
- 	{
- 		var_dump($aPlace);
- 		exit;
- 	}
- 
- 	if ($sOutputFormat=='html')
- 	{
- 		$sDataDate = chksql($oDB->getOne("select TO_CHAR(lastimportdate - '2 minutes'::interval,'YYYY/MM/DD HH24:MI')||' GMT' from import_status limit 1"));
- 		$sTileURL = CONST_Map_Tile_URL;
- 		$sTileAttribution = CONST_Map_Tile_Attribution;
- 	}
- 	include(CONST_BasePath.'/lib/template/address-'.$sOutputFormat.'.php');
+ @define('CONST_ConnectionBucket_PageType', 'Reverse');
+ 
+ require_once(dirname(dirname(__FILE__)).'/settings/settings.php');
+ require_once(CONST_BasePath.'/lib/init-website.php');
+ require_once(CONST_BasePath.'/lib/log.php');
+ require_once(CONST_BasePath.'/lib/PlaceLookup.php');
+ require_once(CONST_BasePath.'/lib/ReverseGeocode.php');
+ require_once(CONST_BasePath.'/lib/output.php');
+ ini_set('memory_limit', '200M');
+ 
+ $oParams = new Nominatim\ParameterParser();
+ 
+ $bAsGeoJSON = $oParams->getBool('polygon_geojson');
+ $bAsKML = $oParams->getBool('polygon_kml');
+ $bAsSVG = $oParams->getBool('polygon_svg');
+ $bAsText = $oParams->getBool('polygon_text');
+ 
+ $iWantedTypes = ($bAsGeoJSON?1:0) + ($bAsKML?1:0) + ($bAsSVG?1:0) + ($bAsText?1:0);
+ if ($iWantedTypes > CONST_PolygonOutput_MaximumTypes) {
+     if (CONST_PolygonOutput_MaximumTypes) {
+         userError("Select only ".CONST_PolygonOutput_MaximumTypes." polgyon output option");
+     } else {
+         userError("Polygon output is disabled");
+     }
+ }
+ 
+ // Polygon simplification threshold (optional)
+ $fThreshold = $oParams->getFloat('polygon_threshold', 0.0);
+ 
+ // Format for output
+ $sOutputFormat = $oParams->getSet('format', array('html', 'xml', 'json', 'jsonv2'), 'xml');
+ 
+ // Preferred language
+ $aLangPrefOrder = $oParams->getPreferredLanguages();
+ 
+ $oDB =& getDB();
+ 
+ $hLog = logStart($oDB, 'reverse', $_SERVER['QUERY_STRING'], $aLangPrefOrder);
+ 
+ 
+ $oPlaceLookup = new Nominatim\PlaceLookup($oDB);
+ $oPlaceLookup->setLanguagePreference($aLangPrefOrder);
+ $oPlaceLookup->setIncludeAddressDetails($oParams->getBool('addressdetails', true));
+ $oPlaceLookup->setIncludeExtraTags($oParams->getBool('extratags', false));
+ $oPlaceLookup->setIncludeNameDetails($oParams->getBool('namedetails', false));
+ 
+ $sOsmType = $oParams->getSet('osm_type', array('N', 'W', 'R'));
+ $iOsmId = $oParams->getInt('osm_id', -1);
+ $fLat = $oParams->getFloat('lat');
+ $fLon = $oParams->getFloat('lon');
+ if ($sOsmType && $iOsmId > 0) {
+     $aPlace = $oPlaceLookup->lookupOSMID($sOsmType, $iOsmId);
+ } elseif ($fLat !== false && $fLon !== false) {
+     $oReverseGeocode = new Nominatim\ReverseGeocode($oDB);
+     $oReverseGeocode->setZoom($oParams->getInt('zoom', 18));
+ 
+     $aLookup = $oReverseGeocode->lookup($fLat, $fLon);
+     if (CONST_Debug) var_dump($aLookup);
+ 
+     $aPlace = $oPlaceLookup->lookup(
+         (int)$aLookup['place_id'],
+         $aLookup['type'],
+         $aLookup['fraction']
+     );
+ } elseif ($sOutputFormat != 'html') {
+     userError("Need coordinates or OSM object to lookup.");
+ }
+ 
+ if ($aPlace) {
+     $oPlaceLookup->setIncludePolygonAsPoints(false);
+     $oPlaceLookup->setIncludePolygonAsText($bAsText);
+     $oPlaceLookup->setIncludePolygonAsGeoJSON($bAsGeoJSON);
+     $oPlaceLookup->setIncludePolygonAsKML($bAsKML);
+     $oPlaceLookup->setIncludePolygonAsSVG($bAsSVG);
+     $oPlaceLookup->setPolygonSimplificationThreshold($fThreshold);
+ 
+     $fRadius = $fDiameter = getResultDiameter($aPlace);
+     $aOutlineResult = $oPlaceLookup->getOutlines(
+         $aPlace['place_id'],
+         $aPlace['lon'],
+         $aPlace['lat'],
+         $fRadius
+     );
+ 
+     if ($aOutlineResult) {
+         $aPlace = array_merge($aPlace, $aOutlineResult);
+     }
+ }
+ 
++logEnd($oDB, $hLog, sizeof($aPlace)?1:0);
+ 
+ if (CONST_Debug) {
+     var_dump($aPlace);
+     exit;
+ }
+ 
+ if ($sOutputFormat=='html') {
+     $sDataDate = chksql($oDB->getOne("select TO_CHAR(lastimportdate - '2 minutes'::interval,'YYYY/MM/DD HH24:MI')||' GMT' from import_status limit 1"));
+     $sTileURL = CONST_Map_Tile_URL;
+     $sTileAttribution = CONST_Map_Tile_Attribution;
+ }
+ include(CONST_BasePath.'/lib/template/address-'.$sOutputFormat.'.php');
++>>>>>>> upstream/master