--- /dev/null
+<?php
+ class Geocode
+ {
+ protected $oDB;
+
+ protected $aLangPrefOrder = array();
+
+ protected $bIncludeAddressDetails = false;
+
+ protected $bIncludePolygonAsPoints = false;
+ protected $bIncludePolygonAsText = false;
+ protected $bIncludePolygonAsGeoJSON = false;
+ protected $bIncludePolygonAsKML = false;
+ protected $bIncludePolygonAsSVG = false;
+
+ protected $aExcludePlaceIDs = array();
+ protected $bDeDupe = true;
+ protected $bReverseInPlan = false;
+
+ protected $iLimit = 20;
+ protected $iFinalLimit = 10;
+ protected $iOffset = 0;
+
+ protected $aCountryCodes = false;
+ protected $aNearPoint = false;
+
+ protected $bBoundedSearch = false;
+ protected $aViewBox = false;
+ protected $aRoutePoints = false;
+
+ protected $iMaxRank = 20;
+ protected $iMinAddressRank = 0;
+ protected $iMaxAddressRank = 30;
+ protected $aAddressRankList = array();
+
+ protected $sAllowedTypesSQLList = false;
+
+ protected $sQuery = false;
+ protected $aStructuredQuery = false;
+
+ function Geocode(&$oDB)
+ {
+ $this->oDB =& $oDB;
+ }
+
+ function setLanguagePreference($aLangPref)
+ {
+ $this->aLangPrefOrder = $aLangPref;
+ }
+
+ function setIncludeAddressDetails($bAddressDetails = true)
+ {
+ $this->bIncludeAddressDetails = (bool)$bAddressDetails;
+ }
+
+ function getIncludeAddressDetails()
+ {
+ return $this->bIncludeAddressDetails;
+ }
+
+ function setIncludePolygonAsPoints($b = true)
+ {
+ $this->bIncludePolygonAsPoints = $b;
+ }
+
+ function getIncludePolygonAsPoints()
+ {
+ return $this->bIncludePolygonAsPoints;
+ }
+
+ function setIncludePolygonAsText($b = true)
+ {
+ $this->bIncludePolygonAsText = $b;
+ }
+
+ function getIncludePolygonAsText()
+ {
+ return $this->bIncludePolygonAsText;
+ }
+
+ function setIncludePolygonAsGeoJSON($b = true)
+ {
+ $this->bIncludePolygonAsGeoJSON = $b;
+ }
+
+ function setIncludePolygonAsKML($b = true)
+ {
+ $this->bIncludePolygonAsKML = $b;
+ }
+
+ function setIncludePolygonAsSVG($b = true)
+ {
+ $this->bIncludePolygonAsSVG = $b;
+ }
+
+ function setDeDupe($bDeDupe = true)
+ {
+ $this->bDeDupe = (bool)$bDeDupe;
+ }
+
+ 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 setOffset($iOffset = 0)
+ {
+ $this->iOffset = $iOffset;
+ }
+
+ function setExcludedPlaceIDs($a)
+ {
+ // TODO: force to int
+ $this->aExcludePlaceIDs = $a;
+ }
+
+ function getExcludedPlaceIDs()
+ {
+ return $this->aExcludePlaceIDs;
+ }
+
+ function setBounded($bBoundedSearch = true)
+ {
+ $this->bBoundedSearch = (bool)$bBoundedSearch;
+ }
+
+ 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 setRoute($aRoutePoints)
+ {
+ $this->aRoutePoints = $aRoutePoints;
+ }
+
+ 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 = (int)$iMin;
+ $this->iMaxAddressRank = (int)$iMax;
+ }
+
+ function setNearPoint($aNearPoint, $fRadiusDeg = 0.1)
+ {
+ $this->aNearPoint = array((float)$aNearPoint[0], (float)$aNearPoint[1], (float)$fRadiusDeg);
+ }
+
+ function setCountryCodesList($aCountryCodes)
+ {
+ $this->aCountryCodes = $aCountryCodes;
+ }
+
+ function setQuery($sQueryString)
+ {
+ $this->sQuery = $sQueryString;
+ $this->aStructuredQuery = false;
+ }
+
+ function getQueryString()
+ {
+ return $this->sQuery;
+ }
+
+ function setStructuredQuery($sAmentiy = false, $sStreet = false, $sCity = false, $sCounty = false, $sState = false, $sCountry = false, $sPostalCode = false)
+ {
+ $this->sQuery = false;
+
+ $this->aStructuredQuery = array();
+ $this->sAllowedTypesSQLList = '';
+
+ loadStructuredAddressElement($this->aStructuredQuery, $this->iMinAddressRank, $this->iMaxAddressRank, $this->aAddressRankList, $sAmentiy, 'amenity', 26, 30, false);
+ loadStructuredAddressElement($this->aStructuredQuery, $this->iMinAddressRank, $this->iMaxAddressRank, $this->aAddressRankList, $sStreet, 'street', 26, 30, false);
+ loadStructuredAddressElement($this->aStructuredQuery, $this->iMinAddressRank, $this->iMaxAddressRank, $this->aAddressRankList, $sCity, 'city', 14, 24, false);
+ loadStructuredAddressElement($this->aStructuredQuery, $this->iMinAddressRank, $this->iMaxAddressRank, $this->aAddressRankList, $sCounty, 'county', 9, 13, false);
+ loadStructuredAddressElement($this->aStructuredQuery, $this->iMinAddressRank, $this->iMaxAddressRank, $this->aAddressRankList, $sState, 'state', 8, 8, false);
+ loadStructuredAddressElement($this->aStructuredQuery, $this->iMinAddressRank, $this->iMaxAddressRank, $this->aAddressRankList, $sCountry, 'country', 4, 4, false);
+ loadStructuredAddressElement($this->aStructuredQuery, $this->iMinAddressRank, $this->iMaxAddressRank, $this->aAddressRankList, $sPostalCode, 'postalcode' , 5, 11, array(5, 11));
+
+ if (sizeof($this->aStructuredQuery) > 0)
+ {
+ $this->sQuery = join(', ', $this->aStructuredQuery);
+ if ($this->iMaxAddressRank < 30)
+ {
+ $sAllowedTypesSQLList = '(\'place\',\'boundary\')';
+ }
+ }
+
+ }
+
+ function getDetails($aPlaceIDs, $iMinAddressRank = 0, $iMaxAddressRank = 30, $aAddressRankList = false, $sAllowedTypesSQLList = false, $bDeDupe = false)
+ {
+ 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(',',$aPlaceIDs);
+
+ $sSQL = "select osm_type,osm_id,class,type,admin_level,rank_search,rank_address,min(place_id) as place_id,calculated_country_code as country_code,";
+ $sSQL .= "get_address_by_language(place_id, $sLanguagePrefArraySQL) as langaddress,";
+ $sSQL .= "get_name_by_language(name, $sLanguagePrefArraySQL) as placename,";
+ $sSQL .= "get_name_by_language(name, ARRAY['ref']) as ref,";
+ $sSQL .= "avg(ST_X(centroid)) as lon,avg(ST_Y(centroid)) as lat, ";
+ $sSQL .= "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(placex.place_id) 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 $iMinAddressRank and $iMaxAddressRank ";
+ if (14 >= $iMinAddressRank && 14 <= $iMaxAddressRank) $sSQL .= " OR (extratags->'place') = 'city'";
+ if ($aAddressRankList) $sSQL .= " OR placex.rank_address in (".join(',',$aAddressRankList).")";
+ $sSQL .= ") ";
+ if ($sAllowedTypesSQLList) $sSQL .= "and placex.class in $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 (!$bDeDupe) $sSQL .= ",place_id";
+ $sSQL .= ",langaddress ";
+ $sSQL .= ",placename ";
+ $sSQL .= ",ref ";
+ $sSQL .= ",extratags->'place' ";
+
+ if (30 >= $iMinAddressRank && 30 <= $iMaxAddressRank)
+ {
+ $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,'us' as country_code,";
+ $sSQL .= "get_address_by_language(place_id, $sLanguagePrefArraySQL) as langaddress,";
+ $sSQL .= "null as placename,";
+ $sSQL .= "null as ref,";
+ $sSQL .= "avg(ST_X(centroid)) as lon,avg(ST_Y(centroid)) as lat, ";
+ $sSQL .= "-0.15 as importance, ";
+ $sSQL .= "(select max(p.importance*(p.rank_address+2)) from place_addressline s, placex p where s.place_id = min(location_property_tiger.place_id) and p.place_id = s.address_place_id and s.isaddress and p.importance is not null) as addressimportance, ";
+ $sSQL .= "null as extra_place ";
+ $sSQL .= "from location_property_tiger where place_id in ($sPlaceIDs) ";
+ $sSQL .= "and 30 between $iMinAddressRank and $iMaxAddressRank ";
+ $sSQL .= "group by place_id";
+ if (!$bDeDupe) $sSQL .= ",place_id";
+ $sSQL .= " union ";
+ $sSQL .= "select 'L' as osm_type,place_id as osm_id,'place' as class,'house' as type,null as admin_level,30 as rank_search,30 as rank_address,min(place_id) as place_id,'us' as country_code,";
+ $sSQL .= "get_address_by_language(place_id, $sLanguagePrefArraySQL) as langaddress,";
+ $sSQL .= "null as placename,";
+ $sSQL .= "null as ref,";
+ $sSQL .= "avg(ST_X(centroid)) as lon,avg(ST_Y(centroid)) as lat, ";
+ $sSQL .= "-0.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.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 $iMinAddressRank and $iMaxAddressRank ";
+ $sSQL .= "group by place_id";
+ if (!$bDeDupe) $sSQL .= ",place_id";
+ $sSQL .= ",get_address_by_language(place_id, $sLanguagePrefArraySQL) ";
+ }
+
+ $sSQL .= "order by importance desc";
+ if (CONST_Debug) { echo "<hr>"; var_dump($sSQL); }
+ $aSearchResults = $this->oDB->getAll($sSQL);
+
+ if (PEAR::IsError($aSearchResults))
+ {
+ failInternalError("Could not get details for place.", $sSQL, $aSearchResults);
+ }
+
+ return $aSearchResults;
+ }
+
+ 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));
+ }
+
+ // Hack to make it handle "new york, ny" (and variants) correctly
+ $sQuery = str_ireplace(array('New York, ny','new york, new york', 'New York ny','new york new york'), 'new york city, ny', $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*(,|$)/',', illinois\1', $sQuery);
+ $sQuery = preg_replace('/,\s*al\s*(,|$)/',', alabama\1', $sQuery);
+ $sQuery = preg_replace('/,\s*la\s*(,|$)/',', louisiana\1', $sQuery);
+ }
+
+ // View Box SQL
+ $sViewboxCentreSQL = $sViewboxSmallSQL = $sViewboxLargeSQL = 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;
+
+ $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)";
+ $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 = false;
+ foreach($this->aRouteaPoints as $aPoint)
+ {
+ if (!$bFirst) $sViewboxCentreSQL .= ",";
+ $sViewboxCentreSQL .= $aPoint[1].' '.$aPoint[0];
+ }
+ $sViewboxCentreSQL .= ")'::geometry,4326)";
+
+ $sSQL = "select st_buffer(".$sViewboxCentreSQL.",".(float)($_GET['routewidth']/69).")";
+ $sViewboxSmallSQL = $this->oDB->getOne($sSQL);
+ if (PEAR::isError($sViewboxSmallSQL))
+ {
+ failInternalError("Could not get small viewbox.", $sSQL, $sViewboxSmallSQL);
+ }
+ $sViewboxSmallSQL = "'".$sViewboxSmallSQL."'::geometry";
+
+ $sSQL = "select st_buffer(".$sViewboxCentreSQL.",".(float)($_GET['routewidth']/30).")";
+ $sViewboxLargeSQL = $this->oDB->getOne($sSQL);
+ if (PEAR::isError($sViewboxLargeSQL))
+ {
+ failInternalError("Could not get large viewbox.", $sSQL, $sViewboxLargeSQL);
+ }
+ $sViewboxLargeSQL = "'".$sViewboxLargeSQL."'::geometry";
+ $bBoundingBoxSearch = $this->bBoundedSearch;
+ }
+
+ // Do we have anything that looks like a lat/lon pair?
+ if (preg_match('/\\b([NS])[ ]+([0-9]+[0-9.]*)[ ]+([0-9.]+)?[, ]+([EW])[ ]+([0-9]+)[ ]+([0-9]+[0-9.]*)?\\b/', $sQuery, $aData))
+ {
+ $fQueryLat = ($aData[1]=='N'?1:-1) * ($aData[2] + $aData[3]/60);
+ $fQueryLon = ($aData[4]=='E'?1:-1) * ($aData[5] + $aData[6]/60);
+ if ($fQueryLat <= 90.1 && $fQueryLat >= -90.1 && $fQueryLon <= 180.1 && $fQueryLon >= -180.1)
+ {
+ $this->setNearPoint(array($fQueryLat, $fQueryLon));
+ $sQuery = trim(str_replace($aData[0], ' ', $sQuery));
+ }
+ }
+ elseif (preg_match('/\\b([0-9]+)[ ]+([0-9]+[0-9.]*)?[ ]+([NS])[, ]+([0-9]+)[ ]+([0-9]+[0-9.]*)?[ ]+([EW])\\b/', $sQuery, $aData))
+ {
+ $fQueryLat = ($aData[3]=='N'?1:-1) * ($aData[1] + $aData[2]/60);
+ $fQueryLon = ($aData[6]=='E'?1:-1) * ($aData[4] + $aData[5]/60);
+ if ($fQueryLat <= 90.1 && $fQueryLat >= -90.1 && $fQueryLon <= 180.1 && $fQueryLon >= -180.1)
+ {
+ $this->setNearPoint(array($fQueryLat, $fQueryLon));
+ $sQuery = trim(str_replace($aData[0], ' ', $sQuery));
+ }
+ }
+ elseif (preg_match('/(\\[|^|\\b)(-?[0-9]+[0-9]*\\.[0-9]+)[, ]+(-?[0-9]+[0-9]*\\.[0-9]+)(\\]|$|\\b)/', $sQuery, $aData))
+ {
+ $fQueryLat = $aData[2];
+ $fQueryLon = $aData[3];
+ if ($fQueryLat <= 90.1 && $fQueryLat >= -90.1 && $fQueryLon <= 180.1 && $fQueryLon >= -180.1)
+ {
+ $this->setNearPoint(array($fQueryLat, $fQueryLon));
+ $sQuery = trim(str_replace($aData[0], ' ', $sQuery));
+ }
+ }
+
+ $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($aStructuredQuery['amenity']) && $aStructuredQuery['amenity'])
+ {
+ $aSpecialTermsRaw[] = array('['.$aStructuredQuery['amenity'].']', $aStructuredQuery['amenity']);
+ unset($aStructuredQuery['amenity']);
+ }
+ foreach($aSpecialTermsRaw as $aSpecialTerm)
+ {
+ $sQuery = str_replace($aSpecialTerm[0], ' ', $sQuery);
+ $sToken = $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 = $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 = $this->oDB->getRow("select make_standard_name('".pg_escape_string($sPhrase)."') as string");
+ if (PEAR::isError($aPhrase))
+ {
+ userError("Illegal query string (not an UTF-8 string): ".$sPhrase);
+ if (CONST_Debug) var_dump($aPhrase);
+ exit;
+ }
+ 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 = $this->oDB->getAll($sSQL);
+ else $aDatabaseWords = array();
+ if (PEAR::IsError($aDatabaseWords))
+ {
+ failInternalError("Could not get word tokens.", $sSQL, $aDatabaseWords);
+ }
+ $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($aStructuredQuery['country'])
+ && strlen($aStructuredQuery['country']) == 2 && strtolower($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
+ $aResultPlaceIDs = array();
+
+ /*
+ 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 $aWordset)
+ {
+ $aWordsetSearches = $aSearches;
+
+ // Add all words from this wordset
+ foreach($aWordset as $iToken => $sToken)
+ {
+ //echo "<br><b>$sToken</b>";
+ $aNewWordsetSearches = array();
+
+ foreach($aWordsetSearches as $aCurrentSearch)
+ {
+ //echo "<i>";
+ //var_dump($aCurrentSearch);
+ //echo "</i>";
+
+ // 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 reverse order is enabled, it may appear at the beginning as well.
+ if (($iToken+1 != sizeof($aWordset) || $iPhrase+1 != sizeof($aPhrases)) &&
+ (!$this->bReverseInPlan || $iToken > 0 || $iPhrase > 0))
+ {
+ $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]) || strlen($sToken) < 4 || 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;
+ 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
+
+ // Do we have a shortcut id?
+ if ($aSearch['sOperator'] == 'name')
+ {
+ $sSQL = "select get_tagpair('".$aSearch['sClass']."', '".$aSearch['sType']."')";
+ if ($iAmenityID = $this->oDB->getOne($sSQL))
+ {
+ $aValidTokens[$aSearch['sClass'].':'.$aSearch['sType']] = array('word_id' => $iAmenityID);
+ $aSearch['aName'][$iAmenityID] = $iAmenityID;
+ $aSearch['sClass'] = '';
+ $aSearch['sType'] = '';
+ }
+ }
+ 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]) || strlen($sToken) < 4 || 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;
+ }
+ }
+ }
+ if (isset($aValidTokens[$sToken]))
+ {
+ // 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']) && strlen($sToken) >= 4)
+ {
+ $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?
+ {
+ 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 ($aSearch['iSearchRank'] < $this->iMaxRank) $aNewWordsetSearches[] = $aSearch;
+ }
+ }
+
+ if (!sizeof($aCurrentSearch['aName']) || $aCurrentSearch['iNamePhrase'] == $iPhrase)
+ {
+ $aSearch = $aCurrentSearch;
+ $aSearch['iSearchRank'] += 2;
+ 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('<hr>',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);
+
+ }
+
+ }
+ 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 ($this->bReverseInPlan)
+ {
+ $aCopyGroupedSearches = $aGroupedSearches;
+ foreach($aCopyGroupedSearches as $iGroup => $aSearches)
+ {
+ foreach($aSearches as $iSearch => $aSearch)
+ {
+ if (sizeof($aSearch['aAddress']))
+ {
+ $iReverseItem = array_pop($aSearch['aAddress']);
+ if (isset($aPossibleMainWordIDs[$iReverseItem]))
+ {
+ $aSearch['aAddress'] = array_merge($aSearch['aAddress'], $aSearch['aName']);
+ $aSearch['aName'] = array($iReverseItem);
+ $aGroupedSearches[$iGroup][] = $aSearch;
+ }
+ //$aReverseSearch['aName'][$iReverseItem] = $iReverseItem;
+ //$aGroupedSearches[$iGroup][] = $aReverseSearch;
+ }
+ }
+ }
+ }
+
+ if (CONST_Search_TryDroppedAddressTerms && sizeof($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++;
+
+ if (CONST_Debug) { echo "<hr><b>Search Loop, group $iGroupLoop, loop $iQueryLoop</b>"; }
+ 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)";
+ $sSQL .= " order by st_area(geometry) desc limit 1";
+ if (CONST_Debug) var_dump($sSQL);
+ $aPlaceIDs = $this->oDB->getCol($sSQL);
+ }
+ }
+ 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 ($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($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 = $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.
+ if (!sizeof($aPlaceIDs) && !sizeof($this->aExcludePlaceIDs))
+ {
+ $sSQL = "select place_id from place_classtype_".$aSearch['sClass']."_".$aSearch['sType']." ct";
+ if ($sCountryCodesSQL) $sSQL .= " join placex using (place_id)";
+ $sSQL .= " where st_contains($sViewboxLargeSQL, 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 = $this->oDB->getCol($sSQL);
+ }
+ }
+ else
+ {
+ $sSQL = "select place_id from placex where class='".$aSearch['sClass']."' and type='".$aSearch['sType']."'";
+ $sSQL .= " and st_contains($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 = $this->oDB->getCol($sSQL);
+ }
+ }
+ }
+ else
+ {
+ $aPlaceIDs = array();
+
+ // First we need a position, either aName or fLat or both
+ $aTerms = array();
+ $aOrder = array();
+
+ // 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']),",")."]";
+ }
+ 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";
+ 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 && $sViewboxSmallSQL";
+ if ($sNearPointSQL) $aOrder[] = "ST_Distance($sNearPointSQL, centroid) asc";
+
+ $sImportanceSQL = '(case when importance = 0 OR importance IS NULL then 0.75-(search_rank::float/40) else importance end)';
+ if ($sViewboxSmallSQL) $sImportanceSQL .= " * case when ST_Contains($sViewboxSmallSQL, centroid) THEN 1 ELSE 0.5 END";
+ if ($sViewboxLargeSQL) $sImportanceSQL .= " * case when ST_Contains($sViewboxLargeSQL, centroid) THEN 1 ELSE 0.5 END";
+ $aOrder[] = "$sImportanceSQL DESC";
+ if (sizeof($aSearch['aFullNameAddress']))
+ {
+ $aOrder[] = '(select count(*) from (select unnest(ARRAY['.join($aSearch['aFullNameAddress'],",").']) INTERSECT select unnest(nameaddress_vector))s) DESC';
+ }
+
+ if (sizeof($aTerms))
+ {
+ $sSQL = "select place_id";
+ $sSQL .= " from search_name";
+ $sSQL .= " where ".join(' and ',$aTerms);
+ $sSQL .= " order by ".join(', ',$aOrder);
+ if ($aSearch['sHouseNumber'] || $aSearch['sClass'])
+ $sSQL .= " limit 50";
+ elseif (!sizeof($aSearch['aName']) && !sizeof($aSearch['aAddress']) && $aSearch['sClass'])
+ $sSQL .= " limit 1";
+ else
+ $sSQL .= " limit ".$this->iLimit;
+
+ if (CONST_Debug) { var_dump($sSQL); }
+ $aViewBoxPlaceIDs = $this->oDB->getAll($sSQL);
+ if (PEAR::IsError($aViewBoxPlaceIDs))
+ {
+ failInternalError("Could not get places for search terms.", $sSQL, $aViewBoxPlaceIDs);
+ }
+ //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'];
+ }
+ }
+ //var_Dump($aPlaceIDs);
+ //exit;
+
+ if ($aSearch['sHouseNumber'] && sizeof($aPlaceIDs))
+ {
+ $aRoadPlaceIDs = $aPlaceIDs;
+ $sPlaceIDs = join(',',$aPlaceIDs);
+
+ // Now they are indexed look for a house attached to a street we found
+ $sHouseNumberRegex = '\\\\m'.str_replace(' ','[-,/ ]',$aSearch['sHouseNumber']).'\\\\M';
+ $sSQL = "select place_id from placex where parent_place_id in (".$sPlaceIDs.") and housenumber ~* E'".$sHouseNumberRegex."'";
+ if (sizeof($this->aExcludePlaceIDs))
+ {
+ $sSQL .= " and place_id not in (".join(',',$this->aExcludePlaceIDs).")";
+ }
+ $sSQL .= " limit $this->iLimit";
+ if (CONST_Debug) var_dump($sSQL);
+ $aPlaceIDs = $this->oDB->getCol($sSQL);
+
+ // If not try the aux fallback table
+ if (!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 place_id not in (".join(',',$this->aExcludePlaceIDs).")";
+ }
+ //$sSQL .= " limit $this->iLimit";
+ if (CONST_Debug) var_dump($sSQL);
+ $aPlaceIDs = $this->oDB->getCol($sSQL);
+ }
+
+ if (!sizeof($aPlaceIDs))
+ {
+ $sSQL = "select place_id from location_property_tiger where parent_place_id in (".$sPlaceIDs.") and housenumber = '".pg_escape_string($aSearch['sHouseNumber'])."'";
+ if (sizeof($this->aExcludePlaceIDs))
+ {
+ $sSQL .= " and place_id not in (".join(',',$this->aExcludePlaceIDs).")";
+ }
+ //$sSQL .= " limit $this->iLimit";
+ if (CONST_Debug) var_dump($sSQL);
+ $aPlaceIDs = $this->oDB->getCol($sSQL);
+ }
+
+ // Fallback to the road
+ if (!sizeof($aPlaceIDs) && preg_match('/[0-9]+/', $aSearch['sHouseNumber']))
+ {
+ $aPlaceIDs = $aRoadPlaceIDs;
+ }
+
+ }
+
+ 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 = $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 = $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)$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 = $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 = $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 ($iOffset) $sSQL .= " offset $iOffset";
+ $sSQL .= " limit $this->iLimit";
+ if (CONST_Debug) var_dump($sSQL);
+ $aClassPlaceIDs = array_merge($aClassPlaceIDs, $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 ($iOffset) $sSQL .= " offset $iOffset";
+ $sSQL .= " limit $this->iLimit";
+ if (CONST_Debug) var_dump($sSQL);
+ $aClassPlaceIDs = array_merge($aClassPlaceIDs, $this->oDB->getCol($sSQL));
+ }
+ }
+ }
+
+ $aPlaceIDs = $aClassPlaceIDs;
+
+ }
+
+ }
+
+ if (PEAR::IsError($aPlaceIDs))
+ {
+ failInternalError("Could not get place IDs from tokens." ,$sSQL, $aPlaceIDs);
+ }
+
+ if (CONST_Debug) { echo "<br><b>Place IDs:</b> "; var_Dump($aPlaceIDs); }
+
+ foreach($aPlaceIDs as $iPlaceID)
+ {
+ $aResultPlaceIDs[$iPlaceID] = $iPlaceID;
+ }
+ 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!)
+ $sSQL = "select place_id from placex where place_id in (".join(',',$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).")";
+ $sSQL .= ") UNION select place_id from location_property_tiger where place_id in (".join(',',$aResultPlaceIDs).") ";
+ $sSQL .= "and (30 between $this->iMinAddressRank and $this->iMaxAddressRank ";
+ if ($this->aAddressRankList) $sSQL .= " OR 30 in (".join(',',$this->aAddressRankList).")";
+ $sSQL .= ")";
+ if (CONST_Debug) var_dump($sSQL);
+ $aResultPlaceIDs = $this->oDB->getCol($sSQL);
+ }
+
+ //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
+ $iPlaceID = geocodeReverse((float)$this->aNearPoint[0], (float)$this->aNearPoint[1]);
+ if ($iPlaceID)
+ $aSearchResults = $this->getDetails(array($iPlaceID));
+ else
+ $aSearchResults = array();
+ }
+
+ // No results? Done
+ if (!sizeof($aSearchResults))
+ {
+ return array();
+ }
+
+ $aClassType = getClassTypesWithImportance();
+ $aRecheckWords = preg_split('/\b/u',$sQuery);
+ foreach($aRecheckWords as $i => $sWord)
+ {
+ if (!$sWord) unset($aRecheckWords[$i]);
+ }
+
+ foreach($aSearchResults as $iResNum => $aResult)
+ {
+ if (CONST_Search_AreaPolygons)
+ {
+ // Get the bounding box and outline polygon
+ $sSQL = "select place_id,0 as numfeatures,st_area(geometry) as area,";
+ $sSQL .= "ST_Y(centroid) as centrelat,ST_X(centroid) as centrelon,";
+ $sSQL .= "ST_Y(ST_PointN(ST_ExteriorRing(Box2D(geometry)),4)) as minlat,ST_Y(ST_PointN(ST_ExteriorRing(Box2D(geometry)),2)) as maxlat,";
+ $sSQL .= "ST_X(ST_PointN(ST_ExteriorRing(Box2D(geometry)),1)) as minlon,ST_X(ST_PointN(ST_ExteriorRing(Box2D(geometry)),3)) as maxlon";
+ if ($this->bIncludePolygonAsGeoJSON) $sSQL .= ",ST_AsGeoJSON(geometry) as asgeojson";
+ if ($this->bIncludePolygonAsKML) $sSQL .= ",ST_AsKML(geometry) as askml";
+ if ($this->bIncludePolygonAsSVG) $sSQL .= ",ST_AsSVG(geometry) as assvg";
+ if ($this->bIncludePolygonAsText || $this->bIncludePolygonAsPoints) $sSQL .= ",ST_AsText(geometry) as astext";
+ $sSQL .= " from placex where place_id = ".$aResult['place_id'].' and st_geometrytype(Box2D(geometry)) = \'ST_Polygon\'';
+ $aPointPolygon = $this->oDB->getRow($sSQL);
+ if (PEAR::IsError($aPointPolygon))
+ {
+ failInternalError("Could not get outline.", $sSQL, $aPointPolygon);
+ }
+
+ if ($aPointPolygon['place_id'])
+ {
+ if ($this->bIncludePolygonAsGeoJSON) $aResult['asgeojson'] = $aPointPolygon['asgeojson'];
+ if ($this->bIncludePolygonAsKML) $aResult['askml'] = $aPointPolygon['askml'];
+ if ($this->bIncludePolygonAsSVG) $aResult['assvg'] = $aPointPolygon['assvg'];
+ if ($this->bIncludePolygonAsText) $aResult['astext'] = $aPointPolygon['astext'];
+
+ if ($aPointPolygon['centrelon'] !== null && $aPointPolygon['centrelat'] !== null )
+ {
+ $aResult['lat'] = $aPointPolygon['centrelat'];
+ $aResult['lon'] = $aPointPolygon['centrelon'];
+ }
+
+ if ($this->bIncludePolygonAsPoints)
+ {
+ // Translate geometary string to point array
+ if (preg_match('#POLYGON\\(\\(([- 0-9.,]+)#',$aPointPolygon['astext'],$aMatch))
+ {
+ preg_match_all('/(-?[0-9.]+) (-?[0-9.]+)/',$aMatch[1],$aPolyPoints,PREG_SET_ORDER);
+ }
+ elseif (preg_match('#MULTIPOLYGON\\(\\(\\(([- 0-9.,]+)#',$aPointPolygon['astext'],$aMatch))
+ {
+ preg_match_all('/(-?[0-9.]+) (-?[0-9.]+)/',$aMatch[1],$aPolyPoints,PREG_SET_ORDER);
+ }
+ elseif (preg_match('#POINT\\((-?[0-9.]+) (-?[0-9.]+)\\)#',$aPointPolygon['astext'],$aMatch))
+ {
+ $fRadius = 0.01;
+ $iSteps = ($fRadius * 40000)^2;
+ $fStepSize = (2*pi())/$iSteps;
+ $aPolyPoints = array();
+ for($f = 0; $f < 2*pi(); $f += $fStepSize)
+ {
+ $aPolyPoints[] = array('',$aMatch[1]+($fRadius*sin($f)),$aMatch[2]+($fRadius*cos($f)));
+ }
+ $aPointPolygon['minlat'] = $aPointPolygon['minlat'] - $fRadius;
+ $aPointPolygon['maxlat'] = $aPointPolygon['maxlat'] + $fRadius;
+ $aPointPolygon['minlon'] = $aPointPolygon['minlon'] - $fRadius;
+ $aPointPolygon['maxlon'] = $aPointPolygon['maxlon'] + $fRadius;
+ }
+ }
+
+ // Output data suitable for display (points and a bounding box)
+ if ($this->bIncludePolygonAsPoints && isset($aPolyPoints))
+ {
+ $aResult['aPolyPoints'] = array();
+ foreach($aPolyPoints as $aPoint)
+ {
+ $aResult['aPolyPoints'][] = array($aPoint[1], $aPoint[2]);
+ }
+ }
+ $aResult['aBoundingBox'] = array($aPointPolygon['minlat'],$aPointPolygon['maxlat'],$aPointPolygon['minlon'],$aPointPolygon['maxlon']);
+ }
+ }
+
+ if ($aResult['extra_place'] == 'city')
+ {
+ $aResult['class'] = 'place';
+ $aResult['type'] = 'city';
+ $aResult['rank_search'] = 16;
+ }
+
+ if (!isset($aResult['aBoundingBox']))
+ {
+ // Default
+ $fDiameter = 0.0001;
+
+ if (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']]['defzoom'];
+ }
+ elseif (isset($aClassType[$aResult['class'].':'.$aResult['type']]['defdiameter'])
+ && $aClassType[$aResult['class'].':'.$aResult['type']]['defdiameter'])
+ {
+ $fDiameter = $aClassType[$aResult['class'].':'.$aResult['type']]['defdiameter'];
+ }
+ $fRadius = $fDiameter / 2;
+
+ $iSteps = max(8,min(100,$fRadius * 3.14 * 100000));
+ $fStepSize = (2*pi())/$iSteps;
+ $aPolyPoints = array();
+ for($f = 0; $f < 2*pi(); $f += $fStepSize)
+ {
+ $aPolyPoints[] = array('',$aResult['lon']+($fRadius*sin($f)),$aResult['lat']+($fRadius*cos($f)));
+ }
+ $aPointPolygon['minlat'] = $aResult['lat'] - $fRadius;
+ $aPointPolygon['maxlat'] = $aResult['lat'] + $fRadius;
+ $aPointPolygon['minlon'] = $aResult['lon'] - $fRadius;
+ $aPointPolygon['maxlon'] = $aResult['lon'] + $fRadius;
+
+ // Output data suitable for display (points and a bounding box)
+ if ($this->bIncludePolygonAsPoints)
+ {
+ $aResult['aPolyPoints'] = array();
+ foreach($aPolyPoints as $aPoint)
+ {
+ $aResult['aPolyPoints'][] = array($aPoint[1], $aPoint[2]);
+ }
+ }
+ $aResult['aBoundingBox'] = array($aPointPolygon['minlat'],$aPointPolygon['maxlat'],$aPointPolygon['minlon'],$aPointPolygon['maxlon']);
+ }
+
+ // 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']]['label'])
+ && $aClassType[$aResult['class'].':'.$aResult['type']]['label'])
+ {
+ $aResult['label'] = $aClassType[$aResult['class'].':'.$aResult['type']]['label'];
+ }
+
+ if ($this->bIncludeAddressDetails)
+ {
+ $aResult['address'] = getAddressDetails($this->oDB, $sLanguagePrefArraySQL, $aResult['place_id'], $aResult['country_code']);
+ if ($aResult['extra_place'] == 'city' && !isset($aResult['address']['city']))
+ {
+ $aResult['address'] = array_merge(array('city' => array_shift(array_values($aResult['address']))), $aResult['address']);
+ }
+ }
+
+ // 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++;
+ }
+
+ $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'];
+ $aResult['foundorder'] = -$aResult['addressimportance'];
+ $aSearchResults[$iResNum] = $aResult;
+ }
+ uasort($aSearchResults, 'byImportance');
+
+ $aOSMIDDone = array();
+ $aClassTypeNameDone = array();
+ $aToFilter = $aSearchResults;
+ $aSearchResults = array();
+
+ $bFirst = true;
+ foreach($aToFilter as $iResNum => $aResult)
+ {
+ if ($aResult['type'] == 'adminitrative') $aResult['type'] = 'administrative';
+ $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
+
+
+/*
+ if (isset($_GET['route']) && $_GET['route'] && isset($_GET['routewidth']) && $_GET['routewidth'])
+ {
+ $aPoints = explode(',',$_GET['route']);
+ if (sizeof($aPoints) % 2 != 0)
+ {
+ userError("Uneven number of points");
+ exit;
+ }
+ $sViewboxCentreSQL = "ST_SetSRID('LINESTRING(";
+ $fPrevCoord = false;
+ }
+*/
--- /dev/null
+<?php
+ class PlaceLookup
+ {
+ protected $oDB;
+
+ protected $iPlaceID;
+
+ protected $aLangPrefOrder = array();
+
+ protected $bAddressDetails = false;
+
+ function PlaceLookup(&$oDB)
+ {
+ $this->oDB =& $oDB;
+ }
+
+ function setLanguagePreference($aLangPrefOrder)
+ {
+ $this->aLangPrefOrder = $aLangPrefOrder;
+ }
+
+ function setIncludeAddressDetails($bAddressDetails = true)
+ {
+ $this->bAddressDetails = $bAddressDetails;
+ }
+
+ function setPlaceID($iPlaceID)
+ {
+ $this->iPlaceID = $iPlaceID;
+ }
+
+ function setOSMID($sType, $iID)
+ {
+ $sSQL = "select place_id from placex where osm_type = '".pg_escape_string($sType)."' and osm_id = ".(int)$iID." order by type = 'postcode' asc";
+ $this->iPlaceID = $this->oDB->getOne($sSQL);
+ }
+
+ function lookup()
+ {
+ if (!$this->iPlaceID) return null;
+
+ $sLanguagePrefArraySQL = "ARRAY[".join(',',array_map("getDBQuoted", $this->aLangPrefOrder))."]";
+
+ $sSQL = "select placex.*,";
+ $sSQL .= " get_address_by_language(place_id, $sLanguagePrefArraySQL) as langaddress,";
+ $sSQL .= " get_name_by_language(name, $sLanguagePrefArraySQL) as placename,";
+ $sSQL .= " get_name_by_language(name, ARRAY['ref']) as ref,";
+ $sSQL .= " st_y(centroid) as lat, st_x(centroid) as lon";
+ $sSQL .= " from placex where place_id = ".(int)$this->iPlaceID;
+ $aPlace = $this->oDB->getRow($sSQL);
+
+ if (!$aPlace['place_id']) return null;
+
+ if ($this->bAddressDetails)
+ {
+ $aAddress = getAddressDetails($this->oDB, $sLanguagePrefArraySQL, $this->iPlaceID, $aPlace['calculated_country_code']);
+ $aPlace['aAddress'] = $aAddress;
+ }
+
+ $aClassType = getClassTypes();
+ $sAddressType = '';
+ $sClassType = $aPlace['class'].':'.$aPlace['type'].':'.$aPlace['admin_level'];
+ if (isset($aClassType[$sClassType]) && isset($aClassType[$sClassType]['simplelabel']))
+ {
+ $sAddressType = $aClassType[$aClassType]['simplelabel'];
+ }
+ else
+ {
+ $sClassType = $aPlace['class'].':'.$aPlace['type'];
+ if (isset($aClassType[$sClassType]) && isset($aClassType[$sClassType]['simplelabel']))
+ $sAddressType = $aClassType[$sClassType]['simplelabel'];
+ else $sAddressType = $aPlace['class'];
+ }
+
+ $aPlace['addresstype'] = $sAddressType;
+
+ return $aPlace;
+ }
+ }
+?>
--- /dev/null
+<?php
+ class ReverseGeocode
+ {
+ protected $oDB;
+
+ protected $fLat;
+ protected $fLon;
+ protected $iMaxRank = 28;
+
+ protected $aLangPrefOrder = array();
+
+ protected $bShowAddressDetails = true;
+
+ function ReverseGeocode(&$oDB)
+ {
+ $this->oDB =& $oDB;
+ }
+
+ function setLanguagePreference($aLangPref)
+ {
+ $this->aLangPrefOrder = $aLangPref;
+ }
+
+ function setIncludeAddressDetails($bAddressDetails = true)
+ {
+ $this->bAddressDetails = $bAddressDetails;
+ }
+
+ function setLatLon($fLat, $fLon)
+ {
+ $this->fLat = (float)$fLat;
+ $this->fLon = (float)$fLon;
+ }
+
+ function setRank($iRank)
+ {
+ $this->iMaxRank = $iRank;
+ }
+
+ function setZoom($iZoom)
+ {
+ // Zoom to rank, this could probably be calculated but a lookup gives fine control
+ $aZoomRank = array(
+ 0 => 2, // Continent / Sea
+ 1 => 2,
+ 2 => 2,
+ 3 => 4, // Country
+ 4 => 4,
+ 5 => 8, // State
+ 6 => 10, // Region
+ 7 => 10,
+ 8 => 12, // County
+ 9 => 12,
+ 10 => 17, // City
+ 11 => 17,
+ 12 => 18, // Town / Village
+ 13 => 18,
+ 14 => 22, // Suburb
+ 15 => 22,
+ 16 => 26, // Street, TODO: major street?
+ 17 => 26,
+ 18 => 30, // or >, Building
+ 19 => 30, // or >, Building
+ );
+ $this->iMaxRank = (isset($iZoom) && isset($aZoomRank[$iZoom]))?$aZoomRank[$iZoom]:28;
+ }
+
+ function lookup()
+ {
+ $sPointSQL = 'ST_SetSRID(ST_Point('.$this->fLon.','.$this->fLat.'),4326)';
+ $iMaxRank = $this->iMaxRank;
+
+ // Find the nearest point
+ $fSearchDiam = 0.0004;
+ $iPlaceID = null;
+ $aArea = false;
+ $fMaxAreaDistance = 1;
+ while(!$iPlaceID && $fSearchDiam < $fMaxAreaDistance)
+ {
+ $fSearchDiam = $fSearchDiam * 2;
+
+ // If we have to expand the search area by a large amount then we need a larger feature
+ // then there is a limit to how small the feature should be
+ if ($fSearchDiam > 2 && $iMaxRank > 4) $iMaxRank = 4;
+ if ($fSearchDiam > 1 && $iMaxRank > 9) $iMaxRank = 8;
+ if ($fSearchDiam > 0.8 && $iMaxRank > 10) $iMaxRank = 10;
+ if ($fSearchDiam > 0.6 && $iMaxRank > 12) $iMaxRank = 12;
+ if ($fSearchDiam > 0.2 && $iMaxRank > 17) $iMaxRank = 17;
+ if ($fSearchDiam > 0.1 && $iMaxRank > 18) $iMaxRank = 18;
+ if ($fSearchDiam > 0.008 && $iMaxRank > 22) $iMaxRank = 22;
+ if ($fSearchDiam > 0.001 && $iMaxRank > 26) $iMaxRank = 26;
+
+ $sSQL = 'select place_id,parent_place_id,rank_search from placex';
+ $sSQL .= ' WHERE ST_DWithin('.$sPointSQL.', geometry, '.$fSearchDiam.')';
+ $sSQL .= ' and rank_search != 28 and rank_search >= '.$iMaxRank;
+ $sSQL .= ' and (name is not null or housenumber is not null)';
+ $sSQL .= ' and class not in (\'waterway\',\'railway\',\'tunnel\',\'bridge\')';
+ $sSQL .= ' and (ST_GeometryType(geometry) not in (\'ST_Polygon\',\'ST_MultiPolygon\') ';
+ $sSQL .= ' and indexed_status = 0 ';
+ $sSQL .= ' OR ST_DWithin('.$sPointSQL.', centroid, '.$fSearchDiam.'))';
+ $sSQL .= ' ORDER BY ST_distance('.$sPointSQL.', geometry) ASC limit 1';
+ if (CONST_Debug) var_dump($sSQL);
+ $aPlace = $this->oDB->getRow($sSQL);
+ if (PEAR::IsError($aPlace))
+ {
+ failInternalError("Could not determine closest place.", $sSQL, $aPlace);
+ }
+ $iPlaceID = $aPlace['place_id'];
+ $iParentPlaceID = $aPlace['parent_place_id'];
+ }
+
+ // The point we found might be too small - use the address to find what it is a child of
+ if ($iPlaceID && $iMaxRank < 28)
+ {
+ if ($aPlace['rank_search'] > 28 && $iParentPlaceID)
+ {
+ $iPlaceID = $iParentPlaceID;
+ }
+ $sSQL = "select address_place_id from place_addressline where place_id = $iPlaceID order by abs(cached_rank_address - $iMaxRank) asc,cached_rank_address desc,isaddress desc,distance desc limit 1";
+ $iPlaceID = $this->oDB->getOne($sSQL);
+ if (PEAR::IsError($iPlaceID))
+ {
+ failInternalError("Could not get parent for place.", $sSQL, $iPlaceID);
+ }
+ if (!$iPlaceID)
+ {
+ $iPlaceID = $aPlace['place_id'];
+ }
+ }
+
+ $oPlaceLookup = new PlaceLookup($this->oDB);
+ $oPlaceLookup->setLanguagePreference($this->aLangPrefOrder);
+ $oPlaceLookup->setIncludeAddressDetails($this->bAddressDetails);
+ $oPlaceLookup->setPlaceId($iPlaceID);
+
+ return $oPlaceLookup->lookup();
+ }
+ }
+?>
if ($aItemListValues) $aAddressRankList = array_merge($aAddressRankList, $aItemListValues);
return true;
}
+
+ function addQuotes($s)
+ {
+ return "'".$s."'";
+ }
if (isset($aPlace['lat'])) $aFilteredPlaces['lat'] = $aPlace['lat'];
if (isset($aPlace['lon'])) $aFilteredPlaces['lon'] = $aPlace['lon'];
$aFilteredPlaces['display_name'] = $aPlace['langaddress'];
- if ($bShowAddressDetails) $aFilteredPlaces['address'] = $aAddress;
+ if ($bShowAddressDetails) $aFilteredPlaces['address'] = $aPlace['aAddress'];
}
javascript_renderData($aFilteredPlaces);
$aFilteredPlaces['display_name'] = $aPlace['langaddress'];
$aFilteredPlaces['name'] = $aPlace['placename'];
- if ($bShowAddressDetails && $aAddress && sizeof($aAddress)) $aFilteredPlaces['address'] = $aAddress;
+ if ($bShowAddressDetails && $aPlace['aAddress'] && sizeof($aPlace['aAddress'])) $aFilteredPlaces['address'] = $aPlace['aAddress'];
}
javascript_renderData($aFilteredPlaces);
if ($bShowAddressDetails) {
echo "<addressparts>";
- foreach($aAddress as $sKey => $sValue)
+ foreach($aPlace['aAddress'] as $sKey => $sValue)
{
$sKey = str_replace(' ','_',$sKey);
echo "<$sKey>";
--- /dev/null
+<?php
+
+ $aOutput = array();
+ $aOutput['licence'] = "Data © OpenStreetMap contributors, ODbL 1.0. http://www.openstreetmap.org/copyright";
+ $aOutput['batch'] = array();
+
+ foreach($aBatchResults as $aSearchResults)
+ {
+ $aFilteredPlaces = array();
+ foreach($aSearchResults as $iResNum => $aPointDetails)
+ {
+ $aPlace = array(
+ 'place_id'=>$aPointDetails['place_id'],
+ );
+
+ $sOSMType = ($aPointDetails['osm_type'] == 'N'?'node':($aPointDetails['osm_type'] == 'W'?'way':($aPointDetails['osm_type'] == 'R'?'relation':'')));
+ if ($sOSMType)
+ {
+ $aPlace['osm_type'] = $sOSMType;
+ $aPlace['osm_id'] = $aPointDetails['osm_id'];
+ }
+
+ if (isset($aPointDetails['aBoundingBox']))
+ {
+ $aPlace['boundingbox'] = array(
+ $aPointDetails['aBoundingBox'][0],
+ $aPointDetails['aBoundingBox'][1],
+ $aPointDetails['aBoundingBox'][2],
+ $aPointDetails['aBoundingBox'][3]);
+
+ if (isset($aPointDetails['aPolyPoints']) && $bShowPolygons)
+ {
+ $aPlace['polygonpoints'] = $aPointDetails['aPolyPoints'];
+ }
+ }
+
+ if (isset($aPointDetails['zoom']))
+ {
+ $aPlace['zoom'] = $aPointDetails['zoom'];
+ }
+
+ $aPlace['lat'] = $aPointDetails['lat'];
+ $aPlace['lon'] = $aPointDetails['lon'];
+ $aPlace['display_name'] = $aPointDetails['name'];
+ $aPlace['place_rank'] = $aPointDetails['rank_search'];
+
+ $aPlace['category'] = $aPointDetails['class'];
+ $aPlace['type'] = $aPointDetails['type'];
+
+ $aPlace['importance'] = $aPointDetails['importance'];
+
+ if (isset($aPointDetails['icon']))
+ {
+ $aPlace['icon'] = $aPointDetails['icon'];
+ }
+
+ if (isset($aPointDetails['address']) && sizeof($aPointDetails['address'])>0)
+ {
+ $aPlace['address'] = $aPointDetails['address'];
+ }
+
+ if (isset($aPointDetails['asgeojson']))
+ {
+ $aPlace['geojson'] = json_decode($aPointDetails['asgeojson']);
+ }
+
+ if (isset($aPointDetails['assvg']))
+ {
+ $aPlace['svg'] = $aPointDetails['assvg'];
+ }
+
+ if (isset($aPointDetails['astext']))
+ {
+ $aPlace['geotext'] = $aPointDetails['astext'];
+ }
+
+ if (isset($aPointDetails['askml']))
+ {
+ $aPlace['geokml'] = $aPointDetails['askml'];
+ }
+
+ $aFilteredPlaces[] = $aPlace;
+ }
+ $aOutput['batch'][] = $aFilteredPlaces;
+ }
+
+ javascript_renderData($aOutput, array('geojson'));
<script src="js/prototype-1.6.0.3.js" type="text/javascript"></script>
<script type="text/javascript">
-
+
var map;
function handleResize()
{
if ($('searchresults'))
{
- var viewwidth = ((document.documentElement.clientWidth > 0?document.documentElement.clientWidth:document.documentElement.offsetWidth) - 200) + 'px';
+ var viewwidth = ((document.documentElement.clientWidth > 0?document.documentElement.clientWidth:document.documentElement.offsetWidth) - 200) + 'px';
$('map').style.width = viewwidth;
$('report').style.width = viewwidth;
}
$('map').style.width = ((document.documentElement.clientWidth > 0?document.documentElement.clientWidth:document.documentElement.offsetWidth) - 0) + 'px';
$('map').style.left = '0px';
}
-
+
if ($('map')) $('map').style.height = ((document.documentElement.clientHeight > 0?document.documentElement.clientHeight:document.documentElement.offsetHeight) - 38) + 'px';
if ($('searchresults')) $('searchresults').style.height = ((document.documentElement.clientHeight > 0?document.documentElement.clientHeight:document.documentElement.offsetHeight) - 38) + 'px';
if ($('report')) $('report').style.height = ((document.documentElement.clientHeight > 0?document.documentElement.clientHeight:document.documentElement.offsetHeight) - 38) + 'px';
</div>
<?php
- if ($sQuery || sizeof($aSearchResults))
+ if ($sQuery)
{
?>
<div id="searchresultsfade1"></div><div id="searchresultsfade2"></div><div id="searchresultsfade3"></div><div id="searchresultsfade4"></div>
the problem relates to missing data, the osm id of the item that is missing. Problems that contain enough detail are likely to get looked at before ones that
require significant research!</p>
</div>
-
+
<!--
<p>Please use this form to report problems with the search results. Of particular interest are items missing, but please also use this form to
report any other problems.</p>
echo " timestamp='".date(DATE_RFC822)."'";
echo " attribution='Data © OpenStreetMap contributors, ODbL 1.0. http://www.openstreetmap.org/copyright'";
echo " querystring='".htmlspecialchars($sQuery, ENT_QUOTES)."'";
- if (isset($_GET['viewbox']) && $_GET['viewbox']) echo " viewbox='".htmlspecialchars($_GET['viewbox'], ENT_QUOTES)."'";
+ if ($sViewBox) echo " viewbox='".htmlspecialchars($sViewBox, ENT_QUOTES)."'";
echo " polygon='".($bShowPolygons?'true':'false')."'";
if (sizeof($aExcludePlaceIDs))
{
--- /dev/null
+#!/usr/bin/php -Cq
+<?php
+
+ // Apache log file
+ $sFile = "sample.log.txt";
+ $sHost1 = 'http://mq-open-search-lm02.ihost.aol.com:8000/nominatim/v1';
+ $sHost2 = 'http://mq-open-search-lm03.ihost.aol.com:8000/nominatim/v1';
+
+
+ $sHost1Escaped = str_replace('/', '\\/', $sHost1);
+ $sHost2Escaped = str_replace('/', '\\/', $sHost2);
+
+ $aToDo = array(251, 293, 328, 399.1, 455.1, 479, 496, 499, 574, 609, 702, 790, 846, 865, 878, 894, 902, 961, 980);
+
+ $hFile = @fopen($sFile, "r");
+ if (!$hFile)
+ {
+ echo "Unable to open file: $sFile\n";
+ exit;
+ }
+
+ $i = 0;
+ while (($sLine = fgets($hFile, 10000)) !== false)
+ {
+ $i++;
+ if (!in_array($i, $aToDo)) continue;
+
+ if (preg_match('#"GET (.*) HTTP/1.[01]"#', $sLine, $aResult))
+ {
+ $sURL1 = $sHost1.$aResult[1];
+ $sURL2 = $sHost2.$aResult[1];
+
+ $sRes1 = '';
+ $k = 0;
+ while(!$sRes1 && $k < 10)
+ {
+ $sRes1 = file_get_contents($sURL1);
+ $k++;
+ if (!$sRes1) sleep(10);
+ }
+ $sRes2 = file_get_contents($sURL2);
+
+ // Strip out the things that will always change
+ $sRes1 = preg_replace('# timestamp=\'[^\']*\'#', '', $sRes1);
+ $sRes1 = str_replace($sHost1, '', $sRes1);
+ $sRes1 = str_replace($sHost1Escaped, '', $sRes1);
+ $sRes2 = preg_replace('# timestamp=\'[^\']*\'#', '', $sRes2);
+ $sRes2 = str_replace($sHost2, '', $sRes2);
+ $sRes2 = str_replace($sHost2Escaped, '', $sRes2);
+
+ if ($sRes1 != $sRes2)
+ {
+ echo "$i:\n";
+ var_dump($sURL1, $sURL2);
+
+ $sRes = $sURL1.":\n";
+ for ($j = 0; $j < strlen($sRes1); $j+=40)
+ {
+ $sRes .= substr($sRes1, $j, 40)."\n";
+ }
+ file_put_contents('log/'.$i.'.1', $sRes);
+
+ $sRes = $sURL2.":\n";
+ for ($j = 0; $j < strlen($sRes2); $j+=40)
+ {
+ $sRes .= substr($sRes2, $j, 40)."\n";
+ }
+ file_put_contents('log/'.$i.'.2', $sRes);
+ }
+ echo ".\n";
+ }
+ else
+ {
+ var_dump($sLine);
+ }
+ }
+
+ fclose($hFile);
if (!file_exists(CONST_Osmosis_Binary))
{
echo "Please download osmosis.\nIf it is already installed, check the path in your local settings (settings/local.php) file.\n";
- fail("osmosis not found in '".CONST_Osmosis_Binary."'");
- }
- if (file_exists(CONST_BasePath.'/settings/configuration.txt'))
- {
- echo "settings/configuration.txt already exists\n";
+ if (!$aCMDResult['all'])
+ {
+ fail("osmosis not found in '".CONST_Osmosis_Binary."'");
+ }
}
else
{
- passthru(CONST_Osmosis_Binary.' --read-replication-interval-init '.CONST_BasePath.'/settings');
- // update osmosis configuration.txt with our settings
- passthru("sed -i 's!baseUrl=.*!baseUrl=".CONST_Replication_Url."!' ".CONST_BasePath.'/settings/configuration.txt');
- passthru("sed -i 's:maxInterval = .*:maxInterval = ".CONST_Replication_MaxInterval.":' ".CONST_BasePath.'/settings/configuration.txt');
- }
-
- // Find the last node in the DB
- $iLastOSMID = $oDB->getOne("select max(id) from planet_osm_nodes");
-
- // Lookup the timestamp that node was created (less 3 hours for margin for changsets to be closed)
- $sLastNodeURL = 'http://www.openstreetmap.org/api/0.6/node/'.$iLastOSMID."/1";
- $sLastNodeXML = file_get_contents($sLastNodeURL);
- preg_match('#timestamp="(([0-9]{4})-([0-9]{2})-([0-9]{2})T([0-9]{2}):([0-9]{2}):([0-9]{2})Z)"#', $sLastNodeXML, $aLastNodeDate);
- $iLastNodeTimestamp = strtotime($aLastNodeDate[1]) - (3*60*60);
-
+ if (file_exists(CONST_BasePath.'/settings/configuration.txt'))
+ {
+ echo "settings/configuration.txt already exists\n";
+ }
+ else
+ {
+ passthru(CONST_Osmosis_Binary.' --read-replication-interval-init '.CONST_BasePath.'/settings');
+ // update osmosis configuration.txt with our settings
+ passthru("sed -i 's!baseUrl=.*!baseUrl=".CONST_Replication_Url."!' ".CONST_BasePath.'/settings/configuration.txt');
+ passthru("sed -i 's:maxInterval = .*:maxInterval = ".CONST_Replication_MaxInterval.":' ".CONST_BasePath.'/settings/configuration.txt');
+ }
- // Search for the correct state file - uses file timestamps so need to sort by date descending
- $sRepURL = CONST_Replication_Url."/";
- $sRep = file_get_contents($sRepURL."?C=M;O=D");
- // download.geofabrik.de: <a href="000/">000/</a></td><td align="right">26-Feb-2013 11:53 </td>
- // planet.openstreetmap.org: <a href="273/">273/</a> 22-Mar-2013 07:41 -
- preg_match_all('#<a href="[0-9]{3}/">([0-9]{3}/)</a>.*(([0-9]{2})-([A-z]{3})-([0-9]{4}) ([0-9]{2}):([0-9]{2}))#', $sRep, $aRepMatches, PREG_SET_ORDER);
- $aPrevRepMatch = false;
- foreach($aRepMatches as $aRepMatch)
- {
- if (strtotime($aRepMatch[2]) < $iLastNodeTimestamp) break;
- $aPrevRepMatch = $aRepMatch;
- }
- if ($aPrevRepMatch) $aRepMatch = $aPrevRepMatch;
+ // Find the last node in the DB
+ $iLastOSMID = $oDB->getOne("select max(id) from planet_osm_nodes");
+
+ // Lookup the timestamp that node was created (less 3 hours for margin for changsets to be closed)
+ $sLastNodeURL = 'http://www.openstreetmap.org/api/0.6/node/'.$iLastOSMID."/1";
+ $sLastNodeXML = file_get_contents($sLastNodeURL);
+ preg_match('#timestamp="(([0-9]{4})-([0-9]{2})-([0-9]{2})T([0-9]{2}):([0-9]{2}):([0-9]{2})Z)"#', $sLastNodeXML, $aLastNodeDate);
+ $iLastNodeTimestamp = strtotime($aLastNodeDate[1]) - (3*60*60);
+
+ // Search for the correct state file - uses file timestamps so need to sort by date descending
+ $sRepURL = CONST_Replication_Url."/";
+ $sRep = file_get_contents($sRepURL."?C=M;O=D");
+ // download.geofabrik.de: <a href="000/">000/</a></td><td align="right">26-Feb-2013 11:53 </td>
+ // planet.openstreetmap.org: <a href="273/">273/</a> 22-Mar-2013 07:41 -
+ preg_match_all('#<a href="[0-9]{3}/">([0-9]{3}/)</a>.*(([0-9]{2})-([A-z]{3})-([0-9]{4}) ([0-9]{2}):([0-9]{2}))#', $sRep, $aRepMatches, PREG_SET_ORDER);
+ $aPrevRepMatch = false;
+ foreach($aRepMatches as $aRepMatch)
+ {
+ if (strtotime($aRepMatch[2]) < $iLastNodeTimestamp) break;
+ $aPrevRepMatch = $aRepMatch;
+ }
+ if ($aPrevRepMatch) $aRepMatch = $aPrevRepMatch;
- $sRepURL .= $aRepMatch[1];
- $sRep = file_get_contents($sRepURL."?C=M;O=D");
- preg_match_all('#<a href="[0-9]{3}/">([0-9]{3}/)</a>.*(([0-9]{2})-([A-z]{3})-([0-9]{4}) ([0-9]{2}):([0-9]{2}))#', $sRep, $aRepMatches, PREG_SET_ORDER);
- $aPrevRepMatch = false;
- foreach($aRepMatches as $aRepMatch)
- {
- if (strtotime($aRepMatch[2]) < $iLastNodeTimestamp) break;
- $aPrevRepMatch = $aRepMatch;
- }
- if ($aPrevRepMatch) $aRepMatch = $aPrevRepMatch;
+ $sRepURL .= $aRepMatch[1];
+ $sRep = file_get_contents($sRepURL."?C=M;O=D");
+ preg_match_all('#<a href="[0-9]{3}/">([0-9]{3}/)</a>.*(([0-9]{2})-([A-z]{3})-([0-9]{4}) ([0-9]{2}):([0-9]{2}))#', $sRep, $aRepMatches, PREG_SET_ORDER);
+ $aPrevRepMatch = false;
+ foreach($aRepMatches as $aRepMatch)
+ {
+ if (strtotime($aRepMatch[2]) < $iLastNodeTimestamp) break;
+ $aPrevRepMatch = $aRepMatch;
+ }
+ if ($aPrevRepMatch) $aRepMatch = $aPrevRepMatch;
- $sRepURL .= $aRepMatch[1];
- $sRep = file_get_contents($sRepURL."?C=M;O=D");
- preg_match_all('#<a href="[0-9]{3}.state.txt">([0-9]{3}).state.txt</a>.*(([0-9]{2})-([A-z]{3})-([0-9]{4}) ([0-9]{2}):([0-9]{2}))#', $sRep, $aRepMatches, PREG_SET_ORDER);
- $aPrevRepMatch = false;
- foreach($aRepMatches as $aRepMatch)
- {
- if (strtotime($aRepMatch[2]) < $iLastNodeTimestamp) break;
- $aPrevRepMatch = $aRepMatch;
+ $sRepURL .= $aRepMatch[1];
+ $sRep = file_get_contents($sRepURL."?C=M;O=D");
+ preg_match_all('#<a href="[0-9]{3}.state.txt">([0-9]{3}).state.txt</a>.*(([0-9]{2})-([A-z]{3})-([0-9]{4}) ([0-9]{2}):([0-9]{2}))#', $sRep, $aRepMatches, PREG_SET_ORDER);
+ $aPrevRepMatch = false;
+ foreach($aRepMatches as $aRepMatch)
+ {
+ if (strtotime($aRepMatch[2]) < $iLastNodeTimestamp) break;
+ $aPrevRepMatch = $aRepMatch;
+ }
+ if ($aPrevRepMatch) $aRepMatch = $aPrevRepMatch;
+
+ $sRepURL .= $aRepMatch[1].'.state.txt';
+ echo "Getting state file: $sRepURL\n";
+ $sStateFile = file_get_contents($sRepURL);
+ if (!$sStateFile || strlen($sStateFile) > 1000) fail("unable to obtain state file");
+ file_put_contents(CONST_BasePath.'/settings/state.txt', $sStateFile);
+ echo "Updating DB status\n";
+ pg_query($oDB->connection, 'TRUNCATE import_status');
+ $sSQL = "INSERT INTO import_status VALUES('".$aRepMatch[2]."')";
+ pg_query($oDB->connection, $sSQL);
}
- if ($aPrevRepMatch) $aRepMatch = $aPrevRepMatch;
-
- $sRepURL .= $aRepMatch[1].'.state.txt';
- echo "Getting state file: $sRepURL\n";
- $sStateFile = file_get_contents($sRepURL);
- if (!$sStateFile || strlen($sStateFile) > 1000) fail("unable to obtain state file");
- file_put_contents(CONST_BasePath.'/settings/state.txt', $sStateFile);
- echo "Updating DB status\n";
- pg_query($oDB->connection, 'TRUNCATE import_status');
- $sSQL = "INSERT INTO import_status VALUES('".$aRepMatch[2]."')";
- pg_query($oDB->connection, $sSQL);
}
if ($aCMDResult['index'] || $aCMDResult['all'])
$sNextFile = $aResult['import-diff'];
if (!file_exists($sNextFile))
{
- echo "Cannot open $nextFile\n";
+ echo "Cannot open $sNextFile\n";
exit;
}
// Don't update the import status - we don't know what this file contains
require_once(dirname(dirname(__FILE__)).'/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');
if (strpos(CONST_BulkUserIPs, ','.$_SERVER["REMOTE_ADDR"].',') !== false)
{
// Preferred language
$aLangPrefOrder = getPreferredLanguages();
- $sLanguagePrefArraySQL = "ARRAY[".join(',',array_map("getDBQuoted",$aLangPrefOrder))."]";
$hLog = logStart($oDB, 'reverse', $_SERVER['QUERY_STRING'], $aLangPrefOrder);
if (isset($_GET['osm_type']) && isset($_GET['osm_id']) && (int)$_GET['osm_id'] && ($_GET['osm_type'] == 'N' || $_GET['osm_type'] == 'W' || $_GET['osm_type'] == 'R'))
{
- $iPlaceID = $oDB->getOne($sSQL = ("select place_id from placex where osm_type = '".$_GET['osm_type']."' and osm_id = ".(int)$_GET['osm_id']." order by type = 'postcode' asc"));
- if (CONST_Debug) var_dump($sSQL);
- if (!$iPlaceID) $sError = 'OSM ID Not Found';
- }
- else
- {
- // Location to look up
- $fLat = (float)$_GET['lat'];
- $fLon = (float)$_GET['lon'];
- $sPointSQL = "ST_SetSRID(ST_Point($fLon,$fLat),4326)";
-
- // Zoom to rank, this could probably be calculated but a lookup gives fine control
- $aZoomRank = array(
- 0 => 2, // Continent / Sea
- 1 => 2,
- 2 => 2,
- 3 => 4, // Country
- 4 => 4,
- 5 => 8, // State
- 6 => 10, // Region
- 7 => 10,
- 8 => 12, // County
- 9 => 12,
- 10 => 17, // City
- 11 => 17,
- 12 => 18, // Town / Village
- 13 => 18,
- 14 => 22, // Suburb
- 15 => 22,
- 16 => 26, // Street, TODO: major street?
- 17 => 26,
- 18 => 30, // or >, Building
- 19 => 30, // or >, Building
- );
- $iMaxRank = (isset($_GET['zoom']) && isset($aZoomRank[$_GET['zoom']]))?$aZoomRank[$_GET['zoom']]:28;
-
- // Find the nearest point
- $fSearchDiam = 0.0004;
- $iPlaceID = null;
- $aArea = false;
- $fMaxAreaDistance = 1;
- while(!$iPlaceID && $fSearchDiam < $fMaxAreaDistance)
- {
- $fSearchDiam = $fSearchDiam * 2;
-
- // If we have to expand the search area by a large amount then we need a larger feature
- // then there is a limit to how small the feature should be
- if ($fSearchDiam > 2 && $iMaxRank > 4) $iMaxRank = 4;
- if ($fSearchDiam > 1 && $iMaxRank > 9) $iMaxRank = 8;
- if ($fSearchDiam > 0.8 && $iMaxRank > 10) $iMaxRank = 10;
- if ($fSearchDiam > 0.6 && $iMaxRank > 12) $iMaxRank = 12;
- if ($fSearchDiam > 0.2 && $iMaxRank > 17) $iMaxRank = 17;
- if ($fSearchDiam > 0.1 && $iMaxRank > 18) $iMaxRank = 18;
- if ($fSearchDiam > 0.008 && $iMaxRank > 22) $iMaxRank = 22;
- if ($fSearchDiam > 0.001 && $iMaxRank > 26) $iMaxRank = 26;
+ $oPlaceLookup = new PlaceLookup($oDB);
+ $oPlaceLookup->setLanguagePreference($aLangPrefOrder);
+ $oPlaceLookup->setIncludeAddressDetails($bShowAddressDetails);
+ $oPlaceLookup->setOSMID($_GET['osm_type'], $_GET['osm_id']);
- $sSQL = 'select place_id,parent_place_id,rank_search from placex';
- $sSQL .= ' WHERE ST_DWithin('.$sPointSQL.', geometry, '.$fSearchDiam.')';
- $sSQL .= ' and rank_search != 28 and rank_search >= '.$iMaxRank;
- $sSQL .= ' and (name is not null or housenumber is not null)';
- $sSQL .= ' and class not in (\'waterway\',\'railway\',\'tunnel\',\'bridge\')';
- $sSQL .= ' and (ST_GeometryType(geometry) not in (\'ST_Polygon\',\'ST_MultiPolygon\') ';
- $sSQL .= ' and indexed_status = 0 ';
- $sSQL .= ' OR ST_DWithin('.$sPointSQL.', centroid, '.$fSearchDiam.'))';
- $sSQL .= ' ORDER BY ST_distance('.$sPointSQL.', geometry) ASC limit 1';
- if (CONST_Debug) var_dump($sSQL);
- $aPlace = $oDB->getRow($sSQL);
- if (PEAR::IsError($aPlace))
- {
- failInternalError("Could not determine closest place.", $sSQL, $aPlace);
- }
- $iPlaceID = $aPlace['place_id'];
- $iParentPlaceID = $aPlace['parent_place_id'];
- }
+ $aPlace = $oPlaceLookup->lookup();
- // The point we found might be too small - use the address to find what it is a child of
- if ($iPlaceID && $iMaxRank < 28)
- {
- if ($aPlace['rank_search'] > 28 && $iParentPlaceID)
- {
- $iPlaceID = $iParentPlaceID;
- }
- $sSQL = "select address_place_id from place_addressline where place_id = $iPlaceID order by abs(cached_rank_address - $iMaxRank) asc,cached_rank_address desc,isaddress desc,distance desc limit 1";
- $iPlaceID = $oDB->getOne($sSQL);
- if (PEAR::IsError($iPlaceID))
- {
- failInternalError("Could not get parent for place.", $sSQL, $iPlaceID);
- }
- if (!$iPlaceID)
- {
- $iPlaceID = $aPlace['place_id'];
- }
- }
+ //if (!$iPlaceID) $sError = 'OSM ID Not Found';
}
-
- if ($iPlaceID)
+ else
{
- $sSQL = "select placex.*,";
- $sSQL .= " get_address_by_language(place_id, $sLanguagePrefArraySQL) as langaddress,";
- $sSQL .= " get_name_by_language(name, $sLanguagePrefArraySQL) as placename,";
- $sSQL .= " get_name_by_language(name, ARRAY['ref']) as ref,";
- $sSQL .= " st_y(centroid) as lat, st_x(centroid) as lon";
- $sSQL .= " from placex where place_id = $iPlaceID ";
-
- $aPlace = $oDB->getRow($sSQL);
- //var_dump($sSQL, $aPlace); exit;
+ $oReverseGeocode = new ReverseGeocode($oDB);
+ $oReverseGeocode->setLanguagePreference($aLangPrefOrder);
+ $oReverseGeocode->setIncludeAddressDetails($bShowAddressDetails);
- if ($bShowAddressDetails)
- {
- $aAddress = getAddressDetails($oDB, $sLanguagePrefArraySQL, $iPlaceID, $aPlace['calculated_country_code']);
- }
- $aClassType = getClassTypes();
- $sAddressType = '';
- $sClassType = $aPlace['class'].':'.$aPlace['type'].':'.$aPlace['admin_level'];
- if (isset($aClassType[$sClassType]) && isset($aClassType[$sClassType]['simplelabel']))
- {
- $sAddressType = $aClassType[$aClassType]['simplelabel'];
- }
- else
- {
- $sClassType = $aPlace['class'].':'.$aPlace['type'];
- if (isset($aClassType[$sClassType]) && isset($aClassType[$sClassType]['simplelabel']))
- $sAddressType = $aClassType[$sClassType]['simplelabel'];
- else $sAddressType = $aPlace['class'];
- }
- $aPlace['addresstype'] = $sAddressType;
+ $oReverseGeocode->setLatLon($_GET['lat'], $_GET['lon']);
+ $oReverseGeocode->setZoom(@$_GET['zoom']);
+ $aPlace = $oReverseGeocode->lookup();
}
if (CONST_Debug) exit;
require_once(dirname(dirname(__FILE__)).'/lib/init-website.php');
require_once(CONST_BasePath.'/lib/log.php');
+ require_once(CONST_BasePath.'/lib/Geocode.php');
ini_set('memory_limit', '200M');
$fLat = CONST_Default_Lat;
$fLon = CONST_Default_Lon;
$iZoom = CONST_Default_Zoom;
- $bBoundingBoxSearch = isset($_GET['bounded'])?(bool)$_GET['bounded']:false;
- $sOutputFormat = 'html';
- $aSearchResults = array();
- $aExcludePlaceIDs = array();
- $sCountryCodesSQL = false;
- $bDeDupe = isset($_GET['dedupe'])?(bool)$_GET['dedupe']:true;
- $bReverseInPlan = false;
- $iFinalLimit = isset($_GET['limit'])?(int)$_GET['limit']:10;
- $iOffset = isset($_GET['offset'])?(int)$_GET['offset']:0;
- $iMaxRank = 20;
- if ($iFinalLimit > 50) $iFinalLimit = 50;
- $iLimit = $iFinalLimit + min($iFinalLimit, 10);
- $iMinAddressRank = 0;
- $iMaxAddressRank = 30;
- $aAddressRankList = array();
- $sAllowedTypesSQLList = false;
+ $sSuggestionURL = false;
- // Format for output
- if (isset($_GET['format']) && ($_GET['format'] == 'html' || $_GET['format'] == 'xml' || $_GET['format'] == 'json' || $_GET['format'] == 'jsonv2'))
- {
- $sOutputFormat = $_GET['format'];
- }
+ $oGeocode =& new Geocode($oDB);
- // Show / use polygons
- $bShowPolygons = (boolean)isset($_GET['polygon']) && $_GET['polygon'];
- if ($sOutputFormat == 'html')
- {
- $bAsText = $bShowPolygons;
- $bShowPolygons = false;
- $bAsGeoJSON = false;
- $bAsKML = false;
- $bAsSVG = false;
- }
- else
- {
- $bAsGeoJSON = (boolean)isset($_GET['polygon_geojson']) && $_GET['polygon_geojson'];
- $bAsKML = (boolean)isset($_GET['polygon_kml']) && $_GET['polygon_kml'];
- $bAsSVG = (boolean)isset($_GET['polygon_svg']) && $_GET['polygon_svg'];
- $bAsText = (boolean)isset($_GET['polygon_text']) && $_GET['polygon_text'];
- if ((($bShowPolygons?1:0)
- + ($bAsGeoJSON?1:0)
- + ($bAsKML?1:0)
- + ($bAsSVG?1:0)
- + ($bAsText?1:0)
- ) > CONST_PolygonOutput_MaximumTypes)
- {
- if (CONST_PolygonOutput_MaximumTypes)
- {
- userError("Select only ".CONST_PolygonOutput_MaximumTypes." polgyon output option");
- }
- else
- {
- userError("Polygon output is disabled");
- }
- exit;
- }
- }
-
- // Show address breakdown
- $bShowAddressDetails = isset($_GET['addressdetails']) && $_GET['addressdetails'];
-
- // Preferred language
$aLangPrefOrder = getPreferredLanguages();
- if (isset($aLangPrefOrder['name:de'])) $bReverseInPlan = true;
- if (isset($aLangPrefOrder['name:ru'])) $bReverseInPlan = true;
- if (isset($aLangPrefOrder['name:ja'])) $bReverseInPlan = true;
+ $oGeocode->setLanguagePreference($aLangPrefOrder);
- $sLanguagePrefArraySQL = "ARRAY[".join(',',array_map("getDBQuoted",$aLangPrefOrder))."]";
-
- if (isset($_GET['exclude_place_ids']) && $_GET['exclude_place_ids'])
+ function loadParamsToGeocode($oGeocode, $aParams, $bBatch = false)
{
- foreach(explode(',',$_GET['exclude_place_ids']) as $iExcludedPlaceID)
- {
- $iExcludedPlaceID = (int)$iExcludedPlaceID;
- if ($iExcludedPlaceID) $aExcludePlaceIDs[$iExcludedPlaceID] = $iExcludedPlaceID;
- }
- }
+ if (isset($aParams['addressdetails'])) $oGeocode->setIncludeAddressDetails((bool)$aParams['addressdetails']);
+ if (isset($aParams['bounded'])) $oGeocode->setBounded((bool)$aParams['bounded']);
+ if (isset($aParams['dedupe'])) $oGeocode->setDedupe((bool)$aParams['dedupe']);
- // Only certain ranks of feature
- if (isset($_GET['featureType']) && !isset($_GET['featuretype'])) $_GET['featuretype'] = $_GET['featureType'];
+ if (isset($aParams['limit'])) $oGeocode->setLimit((int)$aParams['limit']);
+ if (isset($aParams['offset'])) $oGeocode->setOffset((int)$aParams['offset']);
- if (isset($_GET['featuretype']))
- {
- switch($_GET['featuretype'])
+ // List of excluded Place IDs - used for more acurate pageing
+ if (isset($aParams['exclude_place_ids']) && $aParams['exclude_place_ids'])
{
- case 'country':
- $iMinAddressRank = $iMaxAddressRank = 4;
- break;
- case 'state':
- $iMinAddressRank = $iMaxAddressRank = 8;
- break;
- case 'city':
- $iMinAddressRank = 14;
- $iMaxAddressRank = 16;
- break;
- case 'settlement':
- $iMinAddressRank = 8;
- $iMaxAddressRank = 20;
- break;
- }
- }
-
- if (isset($_GET['countrycodes']))
- {
- $aCountryCodes = array();
- foreach(explode(',',$_GET['countrycodes']) as $sCountryCode)
- {
- if (preg_match('/^[a-zA-Z][a-zA-Z]$/', $sCountryCode))
+ foreach(explode(',',$aParams['exclude_place_ids']) as $iExcludedPlaceID)
{
- $aCountryCodes[] = "'".strtolower($sCountryCode)."'";
+ $iExcludedPlaceID = (int)$iExcludedPlaceID;
+ if ($iExcludedPlaceID) $aExcludePlaceIDs[$iExcludedPlaceID] = $iExcludedPlaceID;
}
+ $oGeocode->setExcludedPlaceIds($aExcludePlaceIDs);
}
- $sCountryCodesSQL = join(',', $aCountryCodes);
- }
- // Search query
- $sQuery = (isset($_GET['q'])?trim($_GET['q']):'');
- if (!$sQuery && isset($_SERVER['PATH_INFO']) && $_SERVER['PATH_INFO'][0] == '/')
- {
- $sQuery = substr($_SERVER['PATH_INFO'], 1);
-
- // reverse order of '/' separated string
- $aPhrases = explode('/', $sQuery);
- $aPhrases = array_reverse($aPhrases);
- $sQuery = join(', ',$aPhrases);
- }
+ // Only certain ranks of feature
+ if (isset($aParams['featureType'])) $oGeocode->setFeatureType($aParams['featureType']);
+ if (isset($aParams['featuretype'])) $oGeocode->setFeatureType($aParams['featuretype']);
- // Structured query?
- $aStructuredOptions = array(
- array('amenity', 26, 30, false),
- array('street', 26, 30, false),
- array('city', 14, 24, false),
- array('county', 9, 13, false),
- array('state', 8, 8, false),
- array('country', 4, 4, false),
- array('postalcode', 5, 11, array(5, 11)),
- );
- $aStructuredQuery = array();
- $sAllowedTypesSQLList = '';
- foreach($aStructuredOptions as $aStructuredOption)
- {
- loadStructuredAddressElement($aStructuredQuery, $iMinAddressRank, $iMaxAddressRank, $aAddressRankList, $_GET, $aStructuredOption[0], $aStructuredOption[1], $aStructuredOption[2], $aStructuredOption[3]);
- }
- if (sizeof($aStructuredQuery) > 0)
- {
- $sQuery = join(', ', $aStructuredQuery);
- if ($iMaxAddressRank < 30)
+ // Country code list
+ if (isset($aParams['countrycodes']))
{
- $sAllowedTypesSQLList = '(\'place\',\'boundary\')';
- }
- }
-
- if ($sQuery)
- {
- $hLog = logStart($oDB, 'search', $sQuery, $aLangPrefOrder);
-
- // Hack to make it handle "new york, ny" (and variants) correctly
- $sQuery = str_ireplace(array('New York, ny','new york, new york', 'New York ny','new york new york'), 'new york city, ny', $sQuery);
- if (isset($aLangPrefOrder['name:en']))
- {
- $sQuery = preg_replace('/,\s*il\s*(,|$)/',', illinois\1', $sQuery);
- $sQuery = preg_replace('/,\s*al\s*(,|$)/',', alabama\1', $sQuery);
- $sQuery = preg_replace('/,\s*la\s*(,|$)/',', louisiana\1', $sQuery);
+ $aCountryCodes = array();
+ foreach(explode(',',$aParams['countrycodes']) as $sCountryCode)
+ {
+ if (preg_match('/^[a-zA-Z][a-zA-Z]$/', $sCountryCode))
+ {
+ $aCountryCodes[] = strtolower($sCountryCode);
+ }
+ }
+ $oGeocode->setCountryCodesList($aCountryCodes);
}
- // If we have a view box create the SQL
- // Small is the actual view box, Large is double (on each axis) that
- $sViewboxCentreSQL = $sViewboxSmallSQL = $sViewboxLargeSQL = false;
- if (isset($_GET['viewboxlbrt']) && $_GET['viewboxlbrt'])
- {
- $aCoOrdinatesLBRT = explode(',',$_GET['viewboxlbrt']);
- $_GET['viewbox'] = $aCoOrdinatesLBRT[0].','.$aCoOrdinatesLBRT[3].','.$aCoOrdinatesLBRT[2].','.$aCoOrdinatesLBRT[1];
- }
- if (isset($_GET['viewbox']) && $_GET['viewbox'])
+ if (isset($aParams['viewboxlbrt']) && $aParams['viewboxlbrt'])
{
- $aCoOrdinates = explode(',',$_GET['viewbox']);
- $sViewboxSmallSQL = "ST_SetSRID(ST_MakeBox2D(ST_Point(".(float)$aCoOrdinates[0].",".(float)$aCoOrdinates[1]."),ST_Point(".(float)$aCoOrdinates[2].",".(float)$aCoOrdinates[3].")),4326)";
- $fHeight = $aCoOrdinates[0]-$aCoOrdinates[2];
- $fWidth = $aCoOrdinates[1]-$aCoOrdinates[3];
- $aCoOrdinates[0] += $fHeight;
- $aCoOrdinates[2] -= $fHeight;
- $aCoOrdinates[1] += $fWidth;
- $aCoOrdinates[3] -= $fWidth;
- $sViewboxLargeSQL = "ST_SetSRID(ST_MakeBox2D(ST_Point(".(float)$aCoOrdinates[0].",".(float)$aCoOrdinates[1]."),ST_Point(".(float)$aCoOrdinates[2].",".(float)$aCoOrdinates[3].")),4326)";
+ $aCoOrdinatesLBRT = explode(',',$aParams['viewboxlbrt']);
+ $oGeocode->setViewBox($aCoOrdinatesLBRT[0], $aCoOrdinatesLBRT[1], $aCoOrdinatesLBRT[2], $aCoOrdinatesLBRT[3]);
}
- else
+ else if (isset($aParams['viewbox']) && $aParams['viewbox'])
{
- $bBoundingBoxSearch = false;
+ $aCoOrdinatesLTRB = explode(',',$aParams['viewbox']);
+ $oGeocode->setViewBox($aCoOrdinatesLTRB[0], $aCoOrdinatesLTRB[3], $aCoOrdinatesLTRB[2], $aCoOrdinatesLTRB[1]);
}
- if (isset($_GET['route']) && $_GET['route'] && isset($_GET['routewidth']) && $_GET['routewidth'])
+
+ if (isset($aParams['route']) && $aParams['route'] && isset($aParams['routewidth']) && $aParams['routewidth'])
{
- $aPoints = explode(',',$_GET['route']);
+ $aPoints = explode(',',$aParams['route']);
if (sizeof($aPoints) % 2 != 0)
{
userError("Uneven number of points");
exit;
}
- $sViewboxCentreSQL = "ST_SetSRID('LINESTRING(";
$fPrevCoord = false;
+ $aRoute = array();
foreach($aPoints as $i => $fPoint)
{
if ($i%2)
{
- if ($i != 1) $sViewboxCentreSQL .= ",";
- $sViewboxCentreSQL .= ((float)$fPoint).' '.$fPrevCoord;
+ $aRoute[] = array((float)$fPoint, $fPrevCoord);
}
else
{
$fPrevCoord = (float)$fPoint;
}
}
- $sViewboxCentreSQL .= ")'::geometry,4326)";
-
- $sSQL = "select st_buffer(".$sViewboxCentreSQL.",".(float)($_GET['routewidth']/69).")";
- $sViewboxSmallSQL = $oDB->getOne($sSQL);
- if (PEAR::isError($sViewboxSmallSQL))
- {
- failInternalError("Could not get small viewbox.", $sSQL, $sViewboxSmallSQL);
- }
- $sViewboxSmallSQL = "'".$sViewboxSmallSQL."'::geometry";
-
- $sSQL = "select st_buffer(".$sViewboxCentreSQL.",".(float)($_GET['routewidth']/30).")";
- $sViewboxLargeSQL = $oDB->getOne($sSQL);
- if (PEAR::isError($sViewboxLargeSQL))
- {
- failInternalError("Could not get large viewbox.", $sSQL, $sViewboxLargeSQL);
- }
- $sViewboxLargeSQL = "'".$sViewboxLargeSQL."'::geometry";
- $bBoundingBoxSearch = true;
+ $oGeocode->setRoute($aRoute);
}
- // Do we have anything that looks like a lat/lon pair?
- if (preg_match('/\\b([NS])[ ]+([0-9]+[0-9.]*)[ ]+([0-9.]+)?[, ]+([EW])[ ]+([0-9]+)[ ]+([0-9]+[0-9.]*)?\\b/', $sQuery, $aData))
+ // Search query
+ $sQuery = (isset($aParams['q'])?trim($aParams['q']):'');
+ if (!$sQuery && !$bBatch && isset($_SERVER['PATH_INFO']) && $_SERVER['PATH_INFO'][0] == '/')
{
- $fQueryLat = ($aData[1]=='N'?1:-1) * ($aData[2] + $aData[3]/60);
- $fQueryLon = ($aData[4]=='E'?1:-1) * ($aData[5] + $aData[6]/60);
- if ($fQueryLat <= 90.1 && $fQueryLat >= -90.1 && $fQueryLon <= 180.1 && $fQueryLon >= -180.1)
- {
- $_GET['nearlat'] = $fQueryLat;
- $_GET['nearlon'] = $fQueryLon;
- $sQuery = trim(str_replace($aData[0], ' ', $sQuery));
- }
- }
- elseif (preg_match('/\\b([0-9]+)[ ]+([0-9]+[0-9.]*)?[ ]+([NS])[, ]+([0-9]+)[ ]+([0-9]+[0-9.]*)?[ ]+([EW])\\b/', $sQuery, $aData))
- {
- $fQueryLat = ($aData[3]=='N'?1:-1) * ($aData[1] + $aData[2]/60);
- $fQueryLon = ($aData[6]=='E'?1:-1) * ($aData[4] + $aData[5]/60);
- if ($fQueryLat <= 90.1 && $fQueryLat >= -90.1 && $fQueryLon <= 180.1 && $fQueryLon >= -180.1)
- {
- $_GET['nearlat'] = $fQueryLat;
- $_GET['nearlon'] = $fQueryLon;
- $sQuery = trim(str_replace($aData[0], ' ', $sQuery));
- }
+ $sQuery = substr($_SERVER['PATH_INFO'], 1);
+
+ // reverse order of '/' separated string
+ $aPhrases = explode('/', $sQuery);
+ $aPhrases = array_reverse($aPhrases);
+ $sQuery = join(', ',$aPhrases);
}
- elseif (preg_match('/(\\[|^|\\b)(-?[0-9]+[0-9]*\\.[0-9]+)[, ]+(-?[0-9]+[0-9]*\\.[0-9]+)(\\]|$|\\b)/', $sQuery, $aData))
+ if (!$sQuery)
{
- $fQueryLat = $aData[2];
- $fQueryLon = $aData[3];
- if ($fQueryLat <= 90.1 && $fQueryLat >= -90.1 && $fQueryLon <= 180.1 && $fQueryLon >= -180.1)
- {
- $_GET['nearlat'] = $fQueryLat;
- $_GET['nearlon'] = $fQueryLon;
- $sQuery = trim(str_replace($aData[0], ' ', $sQuery));
- }
+ $oGeocode->setStructuredQuery(@$aParams['amenity'], @$aParams['street'], @$aParams['city'], @$aParams['county'], @$aParams['state'], @$aParams['country'], @$aParams['postalcode']);
}
-
- if ($sQuery || $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'=>'')
- );
-
- $sNearPointSQL = false;
- if (isset($_GET['nearlat']) && isset($_GET['nearlon']))
- {
- $sNearPointSQL = "ST_SetSRID(ST_Point(".(float)$_GET['nearlon'].",".(float)$_GET['nearlat']."),4326)";
- $aSearches[0]['fLat'] = (float)$_GET['nearlat'];
- $aSearches[0]['fLon'] = (float)$_GET['nearlon'];
- $aSearches[0]['fRadius'] = 0.1;
- }
-
- $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($aStructuredQuery['amenity']) && $aStructuredQuery['amenity'])
- {
- $aSpecialTermsRaw[] = array('['.$aStructuredQuery['amenity'].']', $aStructuredQuery['amenity']);
- unset($aStructuredQuery['amenity']);
- }
- foreach($aSpecialTermsRaw as $aSpecialTerm)
- {
- $sQuery = str_replace($aSpecialTerm[0], ' ', $sQuery);
- $sToken = $oDB->getOne("select make_standard_name('".$aSpecialTerm[1]."') as string");
- $sSQL = 'select * from (select word_id,word_token, word, class, type, 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 = $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 (sizeof($aStructuredQuery) > 0)
- {
- $aPhrases = $aStructuredQuery;
- $bStructuredPhrases = true;
- }
- else
- {
- $aPhrases = explode(',',$sQuery);
- $bStructuredPhrases = false;
- }
-
- // Convert each phrase to standard form
- // Create a list of standard words
- // Get all 'sets' of words
- // Generate a complete list of all
- $aTokens = array();
- foreach($aPhrases as $iPhrase => $sPhrase)
- {
- $aPhrase = $oDB->getRow("select make_standard_name('".pg_escape_string($sPhrase)."') as string");
- if (PEAR::isError($aPhrase))
- {
- userError("Illegal query string (not an UTF-8 string): ".$sPhrase);
- if (CONST_Debug) var_dump($aPhrase);
- exit;
- }
- 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
- $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)).')';
- //$sSQL .= ' and search_name_count < '.CONST_Max_Word_Frequency;
- //$sSQL .= ' group by word_token, word, class, type, country_code';
-
- if (CONST_Debug) var_Dump($sSQL);
-
- $aValidTokens = array();
- if (sizeof($aTokens)) $aDatabaseWords = $oDB->getAll($sSQL);
- else $aDatabaseWords = array();
- if (PEAR::IsError($aDatabaseWords))
- {
- failInternalError("Could not get word tokens.", $sSQL, $aDatabaseWords);
- }
- $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($aStructuredQuery['country'])
- && strlen($aStructuredQuery['country']) == 2 && strtolower($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], $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
- $aResultPlaceIDs = array();
-
- /*
- 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 $aWordset)
- {
- $aWordsetSearches = $aSearches;
-
- // Add all words from this wordset
- foreach($aWordset as $iToken => $sToken)
- {
- //echo "<br><b>$sToken</b>";
- $aNewWordsetSearches = array();
-
- foreach($aWordsetSearches as $aCurrentSearch)
- {
- //echo "<i>";
- //var_dump($aCurrentSearch);
- //echo "</i>";
-
- // 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 reverse order is enabled, it may appear at the beginning as well.
- if (($iToken+1 != sizeof($aWordset) || $iPhrase+1 != sizeof($aPhrases)) &&
- (!$bReverseInPlan || $iToken > 0 || $iPhrase > 0))
- {
- $aSearch['iSearchRank'] += 5;
- }
- if ($aSearch['iSearchRank'] < $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'] < $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'] < $iMaxRank) $aNewWordsetSearches[] = $aNewSearch;
- }
-
- if (sizeof($aSearch['aName']))
- {
- if ((!$bStructuredPhrases || $iPhrase > 0) && $sPhraseType != 'country' && (!isset($aValidTokens[$sToken]) || strlen($sToken) < 4 || 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'] < $iMaxRank) $aNewWordsetSearches[] = $aSearch;
- }
-
- }
- elseif (($sPhraseType == '' || $sPhraseType == 'street') && $aSearchTerm['class'] == 'place' && $aSearchTerm['type'] == 'house')
- {
- if ($aSearch['sHouseNumber'] === '')
- {
- $aSearch['sHouseNumber'] = $sToken;
- if ($aSearch['iSearchRank'] < $iMaxRank) $aNewWordsetSearches[] = $aSearch;
- /*
- // Fall back to not searching for this item (better than nothing)
- $aSearch = $aCurrentSearch;
- $aSearch['iSearchRank'] += 1;
- if ($aSearch['iSearchRank'] < $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
-
- // Do we have a shortcut id?
- if ($aSearch['sOperator'] == 'name')
- {
- $sSQL = "select get_tagpair('".$aSearch['sClass']."', '".$aSearch['sType']."')";
- if ($iAmenityID = $oDB->getOne($sSQL))
- {
- $aValidTokens[$aSearch['sClass'].':'.$aSearch['sType']] = array('word_id' => $iAmenityID);
- $aSearch['aName'][$iAmenityID] = $iAmenityID;
- $aSearch['sClass'] = '';
- $aSearch['sType'] = '';
- }
- }
- if ($aSearch['iSearchRank'] < $iMaxRank) $aNewWordsetSearches[] = $aSearch;
- }
- }
- elseif (isset($aSearchTerm['word_id']) && $aSearchTerm['word_id'])
- {
- if (sizeof($aSearch['aName']))
- {
- if ((!$bStructuredPhrases || $iPhrase > 0) && $sPhraseType != 'country' && (!isset($aValidTokens[$sToken]) || strlen($sToken) < 4 || 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'] < $iMaxRank) $aNewWordsetSearches[] = $aSearch;
- }
- }
- }
- if (isset($aValidTokens[$sToken]))
- {
- // 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']) && strlen($sToken) >= 4)
- {
- $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'] < $iMaxRank) $aNewWordsetSearches[] = $aSearch;
- }
- elseif (isset($aValidTokens[' '.$sToken])) // revert to the token version?
- {
- 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'] < $iMaxRank) $aNewWordsetSearches[] = $aSearch;
- }
- }
- }
- else
- {
- $aSearch['aAddressNonSearch'][$aSearchTerm['word_id']] = $aSearchTerm['word_id'];
- if ($aSearch['iSearchRank'] < $iMaxRank) $aNewWordsetSearches[] = $aSearch;
- }
- }
-
- if (!sizeof($aCurrentSearch['aName']) || $aCurrentSearch['iNamePhrase'] == $iPhrase)
- {
- $aSearch = $aCurrentSearch;
- $aSearch['iSearchRank'] += 2;
- if (preg_match('#^[0-9]+$#', $sToken)) $aSearch['iSearchRank'] += 2;
- 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'] < $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('<hr>',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'] < $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);
-
- }
-
- }
- 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'] < $iMaxRank)
- {
- if (!isset($aGroupedSearches[$aSearch['iSearchRank']])) $aGroupedSearches[$aSearch['iSearchRank']] = array();
- $aGroupedSearches[$aSearch['iSearchRank']][] = $aSearch;
- }
- }
- ksort($aGroupedSearches);
- }
-
- if (CONST_Debug) var_Dump($aGroupedSearches);
-
- if ($bReverseInPlan)
- {
- $aCopyGroupedSearches = $aGroupedSearches;
- foreach($aCopyGroupedSearches as $iGroup => $aSearches)
- {
- foreach($aSearches as $iSearch => $aSearch)
- {
- if (sizeof($aSearch['aAddress']))
- {
- $iReverseItem = array_pop($aSearch['aAddress']);
- if (isset($aPossibleMainWordIDs[$iReverseItem]))
- {
- $aSearch['aAddress'] = array_merge($aSearch['aAddress'], $aSearch['aName']);
- $aSearch['aName'] = array($iReverseItem);
- $aGroupedSearches[$iGroup][] = $aSearch;
- }
- //$aReverseSearch['aName'][$iReverseItem] = $iReverseItem;
- //$aGroupedSearches[$iGroup][] = $aReverseSearch;
- }
- }
- }
- }
-
- if (CONST_Search_TryDroppedAddressTerms && sizeof($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++;
-
- if (CONST_Debug) { echo "<hr><b>Search Loop, group $iGroupLoop, loop $iQueryLoop</b>"; }
- if (CONST_Debug) _debugDumpGroupedSearches(array($iGroupedRank => array($aSearch)), $aValidTokens);
-
-
- // Must have a location term
- if (!sizeof($aSearch['aName']) && !sizeof($aSearch['aAddress']) && !$aSearch['fLon'])
- {
- if ($aSearch['sCountryCode'] && !$aSearch['sClass'] && !$aSearch['sHouseNumber'])
- {
- if (4 >= $iMinAddressRank && 4 <= $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)";
- $sSQL .= " order by st_area(geometry) desc limit 1";
- if (CONST_Debug) var_dump($sSQL);
- $aPlaceIDs = $oDB->getCol($sSQL);
- }
- }
- 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 ($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($sViewboxSmallSQL, ct.centroid)";
- if ($sCountryCodesSQL) $sSQL .= " and calculated_country_code in ($sCountryCodesSQL)";
- if (sizeof($aExcludePlaceIDs))
- {
- $sSQL .= " and place_id not in (".join(',',$aExcludePlaceIDs).")";
- }
- if ($sViewboxCentreSQL) $sSQL .= " order by st_distance($sViewboxCentreSQL, ct.centroid) asc";
- $sSQL .= " limit $iLimit";
- if (CONST_Debug) var_dump($sSQL);
- $aPlaceIDs = $oDB->getCol($sSQL);
-
- // If 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.
- if (!sizeof($aPlaceIDs) && !sizeof($aExcludePlaceIDs))
- {
- $sSQL = "select place_id from place_classtype_".$aSearch['sClass']."_".$aSearch['sType']." ct";
- if ($sCountryCodesSQL) $sSQL .= " join placex using (place_id)";
- $sSQL .= " where st_contains($sViewboxLargeSQL, ct.centroid)";
- if ($sCountryCodesSQL) $sSQL .= " and calculated_country_code in ($sCountryCodesSQL)";
- if ($sViewboxCentreSQL) $sSQL .= " order by st_distance($sViewboxCentreSQL, ct.centroid) asc";
- $sSQL .= " limit $iLimit";
- if (CONST_Debug) var_dump($sSQL);
- $aPlaceIDs = $oDB->getCol($sSQL);
- }
- }
- else
- {
- $sSQL = "select place_id from placex where class='".$aSearch['sClass']."' and type='".$aSearch['sType']."'";
- $sSQL .= " and st_contains($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 $iLimit";
- if (CONST_Debug) var_dump($sSQL);
- $aPlaceIDs = $oDB->getCol($sSQL);
- }
- }
- }
- else
- {
- $aPlaceIDs = array();
-
- // First we need a position, either aName or fLat or both
- $aTerms = array();
- $aOrder = array();
-
- // 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']),",")."]";
- }
- 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";
- 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($aExcludePlaceIDs))
- {
- $aTerms[] = "place_id not in (".join(',',$aExcludePlaceIDs).")";
- }
- if ($sCountryCodesSQL)
- {
- $aTerms[] = "country_code in ($sCountryCodesSQL)";
- }
-
- if ($bBoundingBoxSearch) $aTerms[] = "centroid && $sViewboxSmallSQL";
- if ($sNearPointSQL) $aOrder[] = "ST_Distance($sNearPointSQL, centroid) asc";
-
- $sImportanceSQL = '(case when importance = 0 OR importance IS NULL then 0.75-(search_rank::float/40) else importance end)';
- if ($sViewboxSmallSQL) $sImportanceSQL .= " * case when ST_Contains($sViewboxSmallSQL, centroid) THEN 1 ELSE 0.5 END";
- if ($sViewboxLargeSQL) $sImportanceSQL .= " * case when ST_Contains($sViewboxLargeSQL, centroid) THEN 1 ELSE 0.5 END";
- $aOrder[] = "$sImportanceSQL DESC";
- if (sizeof($aSearch['aFullNameAddress']))
- {
- $aOrder[] = '(select count(*) from (select unnest(ARRAY['.join($aSearch['aFullNameAddress'],",").']) INTERSECT select unnest(nameaddress_vector))s) DESC';
- }
-
- if (sizeof($aTerms))
- {
- $sSQL = "select place_id";
- $sSQL .= " from search_name";
- $sSQL .= " where ".join(' and ',$aTerms);
- $sSQL .= " order by ".join(', ',$aOrder);
- if ($aSearch['sHouseNumber'] || $aSearch['sClass'])
- $sSQL .= " limit 50";
- elseif (!sizeof($aSearch['aName']) && !sizeof($aSearch['aAddress']) && $aSearch['sClass'])
- $sSQL .= " limit 1";
- else
- $sSQL .= " limit ".$iLimit;
-
- if (CONST_Debug) { var_dump($sSQL); }
- $aViewBoxPlaceIDs = $oDB->getAll($sSQL);
- if (PEAR::IsError($aViewBoxPlaceIDs))
- {
- failInternalError("Could not get places for search terms.", $sSQL, $aViewBoxPlaceIDs);
- }
- //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'];
- }
- }
- //var_Dump($aPlaceIDs);
- //exit;
-
- if ($aSearch['sHouseNumber'] && sizeof($aPlaceIDs))
- {
- $aRoadPlaceIDs = $aPlaceIDs;
- $sPlaceIDs = join(',',$aPlaceIDs);
-
- // Now they are indexed look for a house attached to a street we found
- $sHouseNumberRegex = '\\\\m'.str_replace(' ','[-,/ ]',$aSearch['sHouseNumber']).'\\\\M';
- $sSQL = "select place_id from placex where parent_place_id in (".$sPlaceIDs.") and housenumber ~* E'".$sHouseNumberRegex."'";
- if (sizeof($aExcludePlaceIDs))
- {
- $sSQL .= " and place_id not in (".join(',',$aExcludePlaceIDs).")";
- }
- $sSQL .= " limit $iLimit";
- if (CONST_Debug) var_dump($sSQL);
- $aPlaceIDs = $oDB->getCol($sSQL);
-
- // If not try the aux fallback table
- if (!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($aExcludePlaceIDs))
- {
- $sSQL .= " and place_id not in (".join(',',$aExcludePlaceIDs).")";
- }
- //$sSQL .= " limit $iLimit";
- if (CONST_Debug) var_dump($sSQL);
- $aPlaceIDs = $oDB->getCol($sSQL);
- }
-
- if (!sizeof($aPlaceIDs))
- {
- $sSQL = "select place_id from location_property_tiger where parent_place_id in (".$sPlaceIDs.") and housenumber = '".pg_escape_string($aSearch['sHouseNumber'])."'";
- if (sizeof($aExcludePlaceIDs))
- {
- $sSQL .= " and place_id not in (".join(',',$aExcludePlaceIDs).")";
- }
- //$sSQL .= " limit $iLimit";
- if (CONST_Debug) var_dump($sSQL);
- $aPlaceIDs = $oDB->getCol($sSQL);
- }
-
- // Fallback to the road
- if (!sizeof($aPlaceIDs) && preg_match('/[0-9]+/', $aSearch['sHouseNumber']))
- {
- $aPlaceIDs = $aRoadPlaceIDs;
- }
-
- }
-
- 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 $iLimit";
- if (CONST_Debug) var_dump($sSQL);
- $aClassPlaceIDs = $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 = $oDB->getOne($sSQL);
-
- $sSQL = "select min(rank_search) from placex where place_id in ($sPlaceIDs)";
-
- if (CONST_Debug) var_dump($sSQL);
- $iMaxRank = ((int)$oDB->getOne($sSQL));
-
- // For state / country level searches the normal radius search doesn't work very well
- $sPlaceGeom = false;
- if ($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 < $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 = $oDB->getOne($sSQL);
- }
-
- if ($sPlaceGeom)
- {
- $sPlaceIDs = false;
- }
- else
- {
- $iMaxRank += 5;
- $sSQL = "select place_id from placex where place_id in ($sPlaceIDs) and rank_search < $iMaxRank";
- if (CONST_Debug) var_dump($sSQL);
- $aPlaceIDs = $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($aExcludePlaceIDs))
- {
- $sSQL .= " and l.place_id not in (".join(',',$aExcludePlaceIDs).")";
- }
- if ($sCountryCodesSQL) $sSQL .= " and lp.calculated_country_code in ($sCountryCodesSQL)";
- if ($sOrderBySQL) $sSQL .= "order by ".$sOrderBySQL." asc";
- if ($iOffset) $sSQL .= " offset $iOffset";
- $sSQL .= " limit $iLimit";
- if (CONST_Debug) var_dump($sSQL);
- $aClassPlaceIDs = array_merge($aClassPlaceIDs, $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($aExcludePlaceIDs))
- {
- $sSQL .= " and l.place_id not in (".join(',',$aExcludePlaceIDs).")";
- }
- if ($sCountryCodesSQL) $sSQL .= " and l.calculated_country_code in ($sCountryCodesSQL)";
- if ($sOrderBy) $sSQL .= "order by ".$OrderBysSQL." asc";
- if ($iOffset) $sSQL .= " offset $iOffset";
- $sSQL .= " limit $iLimit";
- if (CONST_Debug) var_dump($sSQL);
- $aClassPlaceIDs = array_merge($aClassPlaceIDs, $oDB->getCol($sSQL));
- }
- }
- }
-
- $aPlaceIDs = $aClassPlaceIDs;
-
- }
-
- }
-
- if (PEAR::IsError($aPlaceIDs))
- {
- failInternalError("Could not get place IDs from tokens." ,$sSQL, $aPlaceIDs);
- }
-
- if (CONST_Debug) { echo "<br><b>Place IDs:</b> "; var_Dump($aPlaceIDs); }
-
- foreach($aPlaceIDs as $iPlaceID)
- {
- $aResultPlaceIDs[$iPlaceID] = $iPlaceID;
- }
- if ($iQueryLoop > 20) break;
- }
-
- if (isset($aResultPlaceIDs) && sizeof($aResultPlaceIDs) && ($iMinAddressRank != 0 || $iMaxAddressRank != 30))
- {
- // Need to verify passes rank limits before dropping out of the loop (yuk!)
- $sSQL = "select place_id from placex where place_id in (".join(',',$aResultPlaceIDs).") ";
- $sSQL .= "and (placex.rank_address between $iMinAddressRank and $iMaxAddressRank ";
- if (14 >= $iMinAddressRank && 14 <= $iMaxAddressRank) $sSQL .= " OR (extratags->'place') = 'city'";
- if ($aAddressRankList) $sSQL .= " OR placex.rank_address in (".join(',',$aAddressRankList).")";
- $sSQL .= ") UNION select place_id from location_property_tiger where place_id in (".join(',',$aResultPlaceIDs).") ";
- $sSQL .= "and (30 between $iMinAddressRank and $iMaxAddressRank ";
- if ($aAddressRankList) $sSQL .= " OR 30 in (".join(',',$aAddressRankList).")";
- $sSQL .= ")";
- if (CONST_Debug) var_dump($sSQL);
- $aResultPlaceIDs = $oDB->getCol($sSQL);
- }
-
-
- //exit;
- if (isset($aResultPlaceIDs) && sizeof($aResultPlaceIDs)) break;
- if ($iGroupLoop > 4) break;
- if ($iQueryLoop > 30) break;
- }
-
- // Did we find anything?
- if (isset($aResultPlaceIDs) && sizeof($aResultPlaceIDs))
- {
- //var_Dump($aResultPlaceIDs);exit;
- // Get the details for display (is this a redundant extra step?)
- $sPlaceIDs = join(',',$aResultPlaceIDs);
- $sOrderSQL = 'CASE ';
- foreach(array_keys($aResultPlaceIDs) as $iOrder => $iPlaceID)
- {
- $sOrderSQL .= 'when min(place_id) = '.$iPlaceID.' then '.$iOrder.' ';
- }
- $sOrderSQL .= ' ELSE 10000000 END';
- $sSQL = "select osm_type,osm_id,class,type,admin_level,rank_search,rank_address,min(place_id) as place_id,calculated_country_code as country_code,";
- $sSQL .= "get_address_by_language(place_id, $sLanguagePrefArraySQL) as langaddress,";
- $sSQL .= "get_name_by_language(name, $sLanguagePrefArraySQL) as placename,";
- $sSQL .= "get_name_by_language(name, ARRAY['ref']) as ref,";
- $sSQL .= "avg(ST_X(centroid)) as lon,avg(ST_Y(centroid)) as lat, ";
- //$sSQL .= $sOrderSQL." as porder, ";
- $sSQL .= "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(placex.place_id) 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 $iMinAddressRank and $iMaxAddressRank ";
- if (14 >= $iMinAddressRank && 14 <= $iMaxAddressRank) $sSQL .= " OR (extratags->'place') = 'city'";
- if ($aAddressRankList) $sSQL .= " OR placex.rank_address in (".join(',',$aAddressRankList).")";
- $sSQL .= ") ";
- if ($sAllowedTypesSQLList) $sSQL .= "and placex.class in $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 (!$bDeDupe) $sSQL .= ",place_id";
- $sSQL .= ",langaddress ";
- $sSQL .= ",placename ";
- $sSQL .= ",ref ";
- $sSQL .= ",extratags->'place' ";
- $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,'us' as country_code,";
- $sSQL .= "get_address_by_language(place_id, $sLanguagePrefArraySQL) as langaddress,";
- $sSQL .= "null as placename,";
- $sSQL .= "null as ref,";
- $sSQL .= "avg(ST_X(centroid)) as lon,avg(ST_Y(centroid)) as lat, ";
- //$sSQL .= $sOrderSQL." as porder, ";
- $sSQL .= "-0.15 as importance, ";
- $sSQL .= "(select max(p.importance*(p.rank_address+2)) from place_addressline s, placex p where s.place_id = min(location_property_tiger.place_id) and p.place_id = s.address_place_id and s.isaddress and p.importance is not null) as addressimportance, ";
- $sSQL .= "null as extra_place ";
- $sSQL .= "from location_property_tiger where place_id in ($sPlaceIDs) ";
- $sSQL .= "and 30 between $iMinAddressRank and $iMaxAddressRank ";
- $sSQL .= "group by place_id";
- if (!$bDeDupe) $sSQL .= ",place_id";
- $sSQL .= " union ";
- $sSQL .= "select 'L' as osm_type,place_id as osm_id,'place' as class,'house' as type,null as admin_level,30 as rank_search,30 as rank_address,min(place_id) as place_id,'us' as country_code,";
- $sSQL .= "get_address_by_language(place_id, $sLanguagePrefArraySQL) as langaddress,";
- $sSQL .= "null as placename,";
- $sSQL .= "null as ref,";
- $sSQL .= "avg(ST_X(centroid)) as lon,avg(ST_Y(centroid)) as lat, ";
- //$sSQL .= $sOrderSQL." as porder, ";
- $sSQL .= "-0.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.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 $iMinAddressRank and $iMaxAddressRank ";
- $sSQL .= "group by place_id";
- if (!$bDeDupe) $sSQL .= ",place_id";
- $sSQL .= ",get_address_by_language(place_id, $sLanguagePrefArraySQL) ";
- $sSQL .= "order by importance desc";
- //$sSQL .= "order by rank_search,rank_address,porder asc";
- if (CONST_Debug) { echo "<hr>"; var_dump($sSQL); }
- $aSearchResults = $oDB->getAll($sSQL);
- //var_dump($sSQL,$aSearchResults);exit;
-
- if (PEAR::IsError($aSearchResults))
- {
- failInternalError("Could not get details for place.", $sSQL, $aSearchResults);
- }
- }
- } // end if ($sQuery)
else
{
- if (isset($_GET['nearlat']) && trim($_GET['nearlat'])!=='' && isset($_GET['nearlon']) && trim($_GET['nearlon']) !== '')
- {
- $iPlaceID = geocodeReverse((float)$_GET['nearlat'], (float)$_GET['nearlon']);
-
- if ($iPlaceID)
- {
- $aResultPlaceIDs = array($iPlaceID);
- // TODO: this needs refactoring!
-
- // Get the details for display (is this a redundant extra step?)
- $sPlaceIDs = join(',',$aResultPlaceIDs);
- $sOrderSQL = 'CASE ';
- foreach(array_keys($aResultPlaceIDs) as $iOrder => $iPlaceID)
- {
- $sOrderSQL .= 'when min(place_id) = '.$iPlaceID.' then '.$iOrder.' ';
- }
- $sOrderSQL .= ' ELSE 10000000 END';
- $sSQL = "select osm_type,osm_id,class,type,admin_level,rank_search,rank_address,min(place_id) as place_id,calculated_country_code as country_code,";
- $sSQL .= "get_address_by_language(place_id, $sLanguagePrefArraySQL) as langaddress,";
- $sSQL .= "get_name_by_language(name, $sLanguagePrefArraySQL) as placename,";
- $sSQL .= "get_name_by_language(name, ARRAY['ref']) as ref,";
- $sSQL .= "avg(ST_X(centroid)) as lon,avg(ST_Y(centroid)) as lat, ";
- //$sSQL .= $sOrderSQL." as porder, ";
- $sSQL .= "coalesce(importance,0.75-(rank_search::float/40)) as importance, ";
- $sSQL .= "(extratags->'place') as extra_place ";
- $sSQL .= "from placex where place_id in ($sPlaceIDs) ";
- $sSQL .= "and (placex.rank_address between $iMinAddressRank and $iMaxAddressRank ";
- if (14 >= $iMinAddressRank && 14 <= $iMaxAddressRank) $sSQL .= " OR (extratags->'place') = 'city'";
- if ($aAddressRankList) $sSQL .= " OR placex.rank_address in (".join(',',$aAddressRankList).")";
- $sSQL .= ") ";
- $sSQL .= "group by osm_type,osm_id,class,type,admin_level,rank_search,rank_address,calculated_country_code,importance";
- if (!$bDeDupe) $sSQL .= ",place_id";
- $sSQL .= ",get_address_by_language(place_id, $sLanguagePrefArraySQL) ";
- $sSQL .= ",get_name_by_language(name, $sLanguagePrefArraySQL) ";
- $sSQL .= ",get_name_by_language(name, ARRAY['ref']) ";
- $sSQL .= ",extratags->'place' ";
- $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,'us' as country_code,";
- $sSQL .= "get_address_by_language(place_id, $sLanguagePrefArraySQL) as langaddress,";
- $sSQL .= "null as placename,";
- $sSQL .= "null as ref,";
- $sSQL .= "avg(ST_X(centroid)) as lon,avg(ST_Y(centroid)) as lat, ";
- //$sSQL .= $sOrderSQL." as porder, ";
- $sSQL .= "-0.15 as importance, ";
- $sSQL .= "null as extra_place ";
- $sSQL .= "from location_property_tiger where place_id in ($sPlaceIDs) ";
- $sSQL .= "and 30 between $iMinAddressRank and $iMaxAddressRank ";
- $sSQL .= "group by place_id";
- if (!$bDeDupe) $sSQL .= ",place_id";
- $sSQL .= " union ";
- $sSQL .= "select 'L' as osm_type,place_id as osm_id,'place' as class,'house' as type,null as admin_level,30 as rank_search,30 as rank_address,min(place_id) as place_id,'us' as country_code,";
- $sSQL .= "get_address_by_language(place_id, $sLanguagePrefArraySQL) as langaddress,";
- $sSQL .= "null as placename,";
- $sSQL .= "null as ref,";
- $sSQL .= "avg(ST_X(centroid)) as lon,avg(ST_Y(centroid)) as lat, ";
- //$sSQL .= $sOrderSQL." as porder, ";
- $sSQL .= "-0.10 as importance, ";
- $sSQL .= "null as extra_place ";
- $sSQL .= "from location_property_aux where place_id in ($sPlaceIDs) ";
- $sSQL .= "and 30 between $iMinAddressRank and $iMaxAddressRank ";
- $sSQL .= "group by place_id";
- if (!$bDeDupe) $sSQL .= ",place_id";
- $sSQL .= ",get_address_by_language(place_id, $sLanguagePrefArraySQL) ";
- $sSQL .= "order by importance desc";
- //$sSQL .= "order by rank_search,rank_address,porder asc";
- if (CONST_Debug) { echo "<hr>", var_dump($sSQL); }
- $aSearchResults = $oDB->getAll($sSQL);
- //var_dump($sSQL,$aSearchResults);exit;
-
- if (PEAR::IsError($aSearchResults))
- {
- failInternalError("Could not get details for place (near).", $sSQL, $aSearchResults);
- }
- }
- else
- {
- $aSearchResults = array();
- }
- }
+ $oGeocode->setQuery($sQuery);
}
+
}
- $sSearchResult = '';
- if (!sizeof($aSearchResults) && isset($_GET['q']) && $_GET['q'])
+ // Format for output
+ $sOutputFormat = 'html';
+ if (isset($_GET['format']) && ($_GET['format'] == 'html' || $_GET['format'] == 'xml' || $_GET['format'] == 'json' || $_GET['format'] == 'jsonv2'))
{
- $sSearchResult = 'No Results Found';
+ $sOutputFormat = $_GET['format'];
}
- //var_Dump($aSearchResults);
- //exit;
- $aClassType = getClassTypesWithImportance();
- $aRecheckWords = preg_split('/\b/u',$sQuery);
- foreach($aRecheckWords as $i => $sWord)
+
+ // Show / use polygons
+ if ($sOutputFormat == 'html')
{
- if (!$sWord) unset($aRecheckWords[$i]);
+ if (isset($_GET['polygon'])) $oGeocode->setIncludePolygonAsText((bool)$_GET['polygon']);
}
- foreach($aSearchResults as $iResNum => $aResult)
+ else
{
- if (CONST_Search_AreaPolygons)
- {
- // Get the bounding box and outline polygon
- $sSQL = "select place_id,numfeatures,area,outline,";
- $sSQL .= "ST_Y(ST_PointN(ST_ExteriorRing(Box2D(outline)),4)) as minlat,ST_Y(ST_PointN(ST_ExteriorRing(Box2D(outline)),2)) as maxlat,";
- $sSQL .= "ST_X(ST_PointN(ST_ExteriorRing(Box2D(outline)),1)) as minlon,ST_X(ST_PointN(ST_ExteriorRing(Box2D(outline)),3)) as maxlon,";
- $sSQL .= "ST_AsText(outline) as outlinestring from get_place_boundingbox_quick(".$aResult['place_id'].")";
-
- $sSQL = "select place_id,0 as numfeatures,st_area(geometry) as area,";
- $sSQL .= "ST_Y(centroid) as centrelat,ST_X(centroid) as centrelon,";
- $sSQL .= "ST_Y(ST_PointN(ST_ExteriorRing(Box2D(geometry)),4)) as minlat,ST_Y(ST_PointN(ST_ExteriorRing(Box2D(geometry)),2)) as maxlat,";
- $sSQL .= "ST_X(ST_PointN(ST_ExteriorRing(Box2D(geometry)),1)) as minlon,ST_X(ST_PointN(ST_ExteriorRing(Box2D(geometry)),3)) as maxlon";
- if ($bAsGeoJSON) $sSQL .= ",ST_AsGeoJSON(geometry) as asgeojson";
- if ($bAsKML) $sSQL .= ",ST_AsKML(geometry) as askml";
- if ($bAsSVG) $sSQL .= ",ST_AsSVG(geometry) as assvg";
- if ($bAsText || $bShowPolygons) $sSQL .= ",ST_AsText(geometry) as astext";
- $sSQL .= " from placex where place_id = ".$aResult['place_id'].' and st_geometrytype(Box2D(geometry)) = \'ST_Polygon\'';
- $aPointPolygon = $oDB->getRow($sSQL);
- if (PEAR::IsError($aPointPolygon))
- {
- failInternalError("Could not get outline.", $sSQL, $aPointPolygon);
- }
- if ($aPointPolygon['place_id'])
- {
- if ($bAsGeoJSON) $aResult['asgeojson'] = $aPointPolygon['asgeojson'];
- if ($bAsKML) $aResult['askml'] = $aPointPolygon['askml'];
- if ($bAsSVG) $aResult['assvg'] = $aPointPolygon['assvg'];
- if ($bAsText) $aResult['astext'] = $aPointPolygon['astext'];
-
- if ($aPointPolygon['centrelon'] !== null && $aPointPolygon['centrelat'] !== null )
- {
- $aResult['lat'] = $aPointPolygon['centrelat'];
- $aResult['lon'] = $aPointPolygon['centrelon'];
- }
- if ($bShowPolygons)
- {
- // Translate geometary string to point array
- if (preg_match('#POLYGON\\(\\(([- 0-9.,]+)#',$aPointPolygon['astext'],$aMatch))
- {
- preg_match_all('/(-?[0-9.]+) (-?[0-9.]+)/',$aMatch[1],$aPolyPoints,PREG_SET_ORDER);
- }
- elseif (preg_match('#MULTIPOLYGON\\(\\(\\(([- 0-9.,]+)#',$aPointPolygon['astext'],$aMatch))
- {
- preg_match_all('/(-?[0-9.]+) (-?[0-9.]+)/',$aMatch[1],$aPolyPoints,PREG_SET_ORDER);
- }
- elseif (preg_match('#POINT\\((-?[0-9.]+) (-?[0-9.]+)\\)#',$aPointPolygon['astext'],$aMatch))
- {
- $fRadius = 0.01;
- $iSteps = ($fRadius * 40000)^2;
- $fStepSize = (2*pi())/$iSteps;
- $aPolyPoints = array();
- for($f = 0; $f < 2*pi(); $f += $fStepSize)
- {
- $aPolyPoints[] = array('',$aMatch[1]+($fRadius*sin($f)),$aMatch[2]+($fRadius*cos($f)));
- }
- $aPointPolygon['minlat'] = $aPointPolygon['minlat'] - $fRadius;
- $aPointPolygon['maxlat'] = $aPointPolygon['maxlat'] + $fRadius;
- $aPointPolygon['minlon'] = $aPointPolygon['minlon'] - $fRadius;
- $aPointPolygon['maxlon'] = $aPointPolygon['maxlon'] + $fRadius;
- }
- }
-
- // Output data suitable for display (points and a bounding box)
- if ($bShowPolygons && isset($aPolyPoints))
- {
- $aResult['aPolyPoints'] = array();
- foreach($aPolyPoints as $aPoint)
- {
- $aResult['aPolyPoints'][] = array($aPoint[1], $aPoint[2]);
- }
- }
- $aResult['aBoundingBox'] = array($aPointPolygon['minlat'],$aPointPolygon['maxlat'],$aPointPolygon['minlon'],$aPointPolygon['maxlon']);
- }
- }
-
- if ($aResult['extra_place'] == 'city')
- {
- $aResult['class'] = 'place';
- $aResult['type'] = 'city';
- $aResult['rank_search'] = 16;
- }
-
- if (!isset($aResult['aBoundingBox']))
+ $bAsPoints = (boolean)isset($_GET['polygon']) && $_GET['polygon'];
+ $bAsGeoJSON = (boolean)isset($_GET['polygon_geojson']) && $_GET['polygon_geojson'];
+ $bAsKML = (boolean)isset($_GET['polygon_kml']) && $_GET['polygon_kml'];
+ $bAsSVG = (boolean)isset($_GET['polygon_svg']) && $_GET['polygon_svg'];
+ $bAsText = (boolean)isset($_GET['polygon_text']) && $_GET['polygon_text'];
+ if ( ( ($bAsGeoJSON?1:0)
+ + ($bAsKML?1:0)
+ + ($bAsSVG?1:0)
+ + ($bAsText?1:0)
+ + ($bAsPoints?1:0)
+ ) > CONST_PolygonOutput_MaximumTypes)
{
- // Default
- $fDiameter = 0.0001;
-
- if (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']]['defzoom'];
- }
- elseif (isset($aClassType[$aResult['class'].':'.$aResult['type']]['defdiameter'])
- && $aClassType[$aResult['class'].':'.$aResult['type']]['defdiameter'])
- {
- $fDiameter = $aClassType[$aResult['class'].':'.$aResult['type']]['defdiameter'];
- }
- $fRadius = $fDiameter / 2;
-
- $iSteps = max(8,min(100,$fRadius * 3.14 * 100000));
- $fStepSize = (2*pi())/$iSteps;
- $aPolyPoints = array();
- for($f = 0; $f < 2*pi(); $f += $fStepSize)
- {
- $aPolyPoints[] = array('',$aResult['lon']+($fRadius*sin($f)),$aResult['lat']+($fRadius*cos($f)));
- }
- $aPointPolygon['minlat'] = $aResult['lat'] - $fRadius;
- $aPointPolygon['maxlat'] = $aResult['lat'] + $fRadius;
- $aPointPolygon['minlon'] = $aResult['lon'] - $fRadius;
- $aPointPolygon['maxlon'] = $aResult['lon'] + $fRadius;
-
- // Output data suitable for display (points and a bounding box)
- if ($bShowPolygons)
+ if (CONST_PolygonOutput_MaximumTypes)
{
- $aResult['aPolyPoints'] = array();
- foreach($aPolyPoints as $aPoint)
- {
- $aResult['aPolyPoints'][] = array($aPoint[1], $aPoint[2]);
- }
+ userError("Select only ".CONST_PolygonOutput_MaximumTypes." polgyon output option");
}
- $aResult['aBoundingBox'] = array($aPointPolygon['minlat'],$aPointPolygon['maxlat'],$aPointPolygon['minlon'],$aPointPolygon['maxlon']);
- }
-
- // 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']]['label'])
- && $aClassType[$aResult['class'].':'.$aResult['type']]['label'])
- {
- $aResult['label'] = $aClassType[$aResult['class'].':'.$aResult['type']]['label'];
- }
-
- if ($bShowAddressDetails)
- {
- $aResult['address'] = getAddressDetails($oDB, $sLanguagePrefArraySQL, $aResult['place_id'], $aResult['country_code']);
- if ($aResult['extra_place'] == 'city' && !isset($aResult['address']['city']))
+ else
{
- $aResult['address'] = array_merge(array('city' => array_shift(array_values($aResult['address']))), $aResult['address']);
+ userError("Polygon output is disabled");
}
-
- //var_dump($aResult['address']);
- //exit;
- }
-
- // 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++;
+ exit;
}
-
- $aResult['importance'] = $aResult['importance'] + ($iCountWords*0.1); // 0.1 is a completely arbitrary number but something in the range 0.1 to 0.5 would seem right
-
- //if (CONST_Debug) var_dump($aResult['class'].':'.$aResult['type'].':'.$aResult['admin_level']);
- /*
- if (isset($aClassType[$aResult['class'].':'.$aResult['type'].':'.$aResult['admin_level']]['importance'])
- && $aClassType[$aResult['class'].':'.$aResult['type'].':'.$aResult['admin_level']]['importance'])
- {
- $aResult['importance'] = $aClassType[$aResult['class'].':'.$aResult['type'].':'.$aResult['admin_level']]['importance'];
- }
- elseif (isset($aClassType[$aResult['class'].':'.$aResult['type']]['importance'])
- && $aClassType[$aResult['class'].':'.$aResult['type']]['importance'])
- {
- $aResult['importance'] = $aClassType[$aResult['class'].':'.$aResult['type']]['importance'];
- }
- else
- {
- $aResult['importance'] = 1000000000000000;
- }
- */
- $aResult['name'] = $aResult['langaddress'];
- $aResult['foundorder'] = -$aResult['addressimportance'];
- $aSearchResults[$iResNum] = $aResult;
+ $oGeocode->setIncludePolygonAsPoints($bAsPoints);
+ $oGeocode->setIncludePolygonAsText($bAsText);
+ $oGeocode->setIncludePolygonAsGeoJSON($bAsGeoJSON);
+ $oGeocode->setIncludePolygonAsKML($bAsKML);
+ $oGeocode->setIncludePolygonAsSVG($bAsSVG);
}
- uasort($aSearchResults, 'byImportance');
- $aOSMIDDone = array();
- $aClassTypeNameDone = array();
- $aToFilter = $aSearchResults;
- $aSearchResults = array();
+ loadParamsToGeocode($oGeocode, $_GET, false);
- $bFirst = true;
- foreach($aToFilter as $iResNum => $aResult)
+ if (isset($_GET['batch']))
{
- if ($aResult['type'] == 'adminitrative') $aResult['type'] = 'administrative';
- $aExcludePlaceIDs[$aResult['place_id']] = $aResult['place_id'];
- if ($bFirst)
+ $aBatch = json_decode($_GET['batch'], true);
+ $aBatchResults = array();
+ foreach($aBatch as $aBatchParams)
{
- $fLat = $aResult['lat'];
- $fLon = $aResult['lon'];
- if (isset($aResult['zoom'])) $iZoom = $aResult['zoom'];
- $bFirst = false;
+ $oBatchGeocode = clone $oGeocode;
+ loadParamsToGeocode($oBatchGeocode, $aBatchParams, true);
+ $aSearchResults = $oBatchGeocode->lookup();
+ $aBatchResults[] = $aSearchResults;
}
- if (!$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) >= $iFinalLimit) break;
+ include(CONST_BasePath.'/lib/template/search-batch-json.php');
+ exit;
}
+ $hLog = logStart($oDB, 'search', $oGeocode->getQueryString(), $aLangPrefOrder);
+
+ $aSearchResults = $oGeocode->lookup();
+ if ($aSearchResults === false) $aSearchResults = array();
+
$sDataDate = $oDB->getOne("select TO_CHAR(lastimportdate - '2 minutes'::interval,'YYYY/MM/DD HH24:MI')||' GMT' from import_status limit 1");
- if (isset($_GET['nearlat']) && isset($_GET['nearlon']))
- {
- $sQuery .= ' ['.$_GET['nearlat'].','.$_GET['nearlon'].']';
- }
+ logEnd($oDB, $hLog, sizeof($aSearchResults));
- if ($sQuery)
- {
- logEnd($oDB, $hLog, sizeof($aToFilter));
- }
- $sMoreURL = CONST_Website_BaseURL.'search?format='.urlencode($sOutputFormat).'&exclude_place_ids='.join(',',$aExcludePlaceIDs);
+ $bAsText = $oGeocode->getIncludePolygonAsText();
+ $sQuery = $oGeocode->getQueryString();
+ $sViewBox = $oGeocode->getViewBoxString();
+ $bShowPolygons = (isset($_GET['polygon']) && $_GET['polygon']);
+ $aExcludePlaceIDs = $oGeocode->getExcludedPlaceIDs();
+
+ $sMoreURL = CONST_Website_BaseURL.'search?format='.urlencode($sOutputFormat).'&exclude_place_ids='.join(',',$oGeocode->getExcludedPlaceIDs());
if (isset($_SERVER["HTTP_ACCEPT_LANGUAGE"])) $sMoreURL .= '&accept-language='.$_SERVER["HTTP_ACCEPT_LANGUAGE"];
if ($bShowPolygons) $sMoreURL .= '&polygon=1';
- if ($bShowAddressDetails) $sMoreURL .= '&addressdetails=1';
- if (isset($_GET['viewbox']) && $_GET['viewbox']) $sMoreURL .= '&viewbox='.urlencode($_GET['viewbox']);
+ if ($oGeocode->getIncludeAddressDetails()) $sMoreURL .= '&addressdetails=1';
+ if ($sViewBox) $sMoreURL .= '&viewbox='.urlencode($sViewBox);
if (isset($_GET['nearlat']) && isset($_GET['nearlon'])) $sMoreURL .= '&nearlat='.(float)$_GET['nearlat'].'&nearlon='.(float)$_GET['nearlon'];
$sMoreURL .= '&q='.urlencode($sQuery);
--- /dev/null
+DROP TABLE entity;
+DROP TABLE entity_label;
+DROP TABLE entity_description;
+DROP TABLE entity_alias;
+DROP TABLE entity_link;
+DROP TABLE entity_property;
+
+CREATE TABLE entity (
+ entity_id bigint,
+ title text,
+ pid bigint,
+ qid bigint,
+ datatype text,
+ CONSTRAINT pk_entity PRIMARY KEY(entity_id)
+);
+
+CREATE TABLE entity_label (
+ entity_id bigint,
+ language text,
+ label text,
+ CONSTRAINT pk_entity_label PRIMARY KEY(entity_id,language)
+);
+
+CREATE TABLE entity_description (
+ entity_id bigint,
+ language text,
+ description text,
+ CONSTRAINT pk_entity_description PRIMARY KEY(entity_id,language)
+);
+
+CREATE TABLE entity_alias (
+ entity_id bigint,
+ language text,
+ alias text,
+ CONSTRAINT pk_entity_alias PRIMARY KEY(entity_id,language,alias)
+);
+
+CREATE TABLE entity_link (
+ entity_id bigint,
+ target text,
+ value text,
+ CONSTRAINT pk_entity_link PRIMARY KEY(entity_id,target)
+);
+
+CREATE TABLE entity_link_hit (
+ entity_id bigint,
+ target text,
+ value text,
+ hits bigint,
+ CONSTRAINT pk_entity_link_hit PRIMARY KEY(entity_id,target)
+);
+
+CREATE TABLE link_hit (
+ target text,
+ value text,
+ hits bigint,
+ CONSTRAINT pk_link_hit PRIMARY KEY(target,value)
+);
+
+CREATE TABLE entity_property (
+ entity_id bigint,
+ order_id bigint,
+ pid bigint,
+ string text,
+ toqid bigint,
+ location geometry,
+ datetime timestamp with time zone,
+ CONSTRAINT pk_entity_property PRIMARY KEY(entity_id, order_id)
+);
+
+CREATE TABLE import_link_hit (
+ target text,
+ value text,
+ hits bigint
+);
--- /dev/null
+PSQL=/usr/lib/postgresql/9.2/bin/psql -d wikidata
+
+cat create.sql | $PSQL
+
+cat entity.csv | $PSQL -c "COPY entity from STDIN WITH CSV"
+cat entity_label.csv | $PSQL -c "COPY entity_label from STDIN WITH CSV"
+cat entity_description.csv | $PSQL -c "COPY entity_description from STDIN WITH CSV"
+cat entity_alias.csv | $PSQL -c "COPY entity_alias from STDIN WITH CSV"
+cat entity_link.csv | $PSQL -c "COPY entity_link from STDIN WITH CSV"
+cat entity_property.csv | $PSQL -c "COPY entity_property from STDIN WITH CSV"
+
+$PSQL -c "create index idx_entity_link_target on entity_link using btree (target,value)"
+$PSQL -c "create index idx_entity_qid on entity using btree (qid)"
+$PSQL -c "create table property_label_en as select pid,null::text as label from entity where pid is not null"
+$PSQL -c "update property_label_en set label = x.label from (select pid,label,language from entity join entity_label using (entity_id) where pid is not null and language = 'en') as x where x.pid = property_label_en.pid"
+$PSQL -c "create unique index idx_property_label_en on property_label_en using btree (pid)"
+$PSQL -c "alter table entity add column label_en text"
+$PSQL -c "update entity set label_en = label from entity_label where entity.entity_id = entity_label.entity_id and language = 'en'"
+$PSQL -c "alter table entity add column description_en text"
+$PSQL -c "update entity set description_en = description from entity_description where entity.entity_id = entity_description.entity_id and language = 'en'"
+
+cat totals.txt | $PSQL -c "COPY import_link_hit from STDIN WITH CSV DELIMITER ' '"
+$PSQL -c "insert into link_hit select target||'wiki', catch_decode_url_part(value), sum(hits) from import_link_hit group by target||'wiki', catch_decode_url_part(value)"
+$PSQL -c "insert into entity_link_hit select entity_id, target, value, coalesce(hits,0) from entity_link left outer join link_hit using (target, value)"
--- /dev/null
+#!/usr/bin/php -Cq
+<?php
+
+$hFile = @fopen("wikidatawiki-20130623-pages-articles.xml", "r");
+
+$hFileEntity = fopen("entity.csv", "w");
+$hFileEntityLabel = fopen("entity_label.csv", "w");
+$hFileEntityDescription = fopen("entity_description.csv", "w");
+$hFileEntityAlias = fopen("entity_alias.csv", "w");
+$hFileEntityLink = fopen("entity_link.csv", "w");
+$hFileEntityProperty = fopen("entity_property.csv", "w");
+
+$iCount = 0;
+
+$sTitle = '';
+$iNS = false;
+$iID = false;
+
+if ($hFile) {
+ while (($sLine = fgets($hFile, 4000000)) !== false) {
+ if (substr($sLine, 0, 11) == ' <title>') {
+ $sTitle = substr($sLine, 11, -9);
+ }
+ else if (substr($sLine, 0, 8) == ' <ns>') {
+ $iNS = (int)substr($sLine, 8, -6);
+ }
+ else if (substr($sLine, 0, 8) == ' <id>') {
+ $iID = (int)substr($sLine, 8, -6);
+ }
+ else if (substr($sLine, 0, 33) == ' <text xml:space="preserve">') {
+ if ($iNS == -2) continue;
+ if ($iNS == -1) continue;
+ if ($iNS == 1) continue;
+ if ($iNS == 2) continue;
+ if ($iNS == 3) continue;
+ if ($iNS == 4) continue;
+ if ($iNS == 5) continue;
+ if ($iNS == 6) continue;
+ if ($iNS == 7) continue;
+ if ($iNS == 8) continue;
+ if ($iNS == 9) continue;
+ if ($iNS == 10) continue;
+ if ($iNS == 11) continue;
+ if ($iNS == 12) continue;
+ if ($iNS == 13) continue;
+ if ($iNS == 14) continue;
+ if ($iNS == 15) continue;
+ if ($iNS == 121) continue;
+ if ($iNS == 123) continue;
+ if ($iNS == 829) continue;
+ if ($iNS == 1198) continue;
+ if ($iNS == 1199) continue;
+ $sText = html_entity_decode(substr($sLine, 33, -8), ENT_COMPAT, 'UTF-8');
+ $aArticle = json_decode($sText, true);
+
+ if (array_diff(array_keys($aArticle), array("label", "description", "aliases", "links", "entity", "claims", "datatype")) != array()) {
+ // DEBUG
+ var_dump($sTitle);
+ var_dump(array_keys($aArticle));
+ var_dump($aArticle);
+ exit;
+ }
+
+ $iPID = $iQID = null;
+ if ($aArticle['entity'][0] == 'p') {
+ $iPID = (int)substr($aArticle['entity'], 1);
+ } else if ($aArticle['entity'][0] == 'q') {
+ $iQID = (int)substr($aArticle['entity'], 1);
+ } else {
+ continue;
+ }
+
+ echo ".";
+
+ fputcsv($hFileEntity, array($iID,$sTitle,$iPID,$iQID,@$aArticle['datatype']));
+
+ foreach($aArticle['label'] as $sLang => $sLabel) {
+ fputcsv($hFileEntityLabel, array($iID,$sLang,$sLabel));
+ // echo "insert into entity_label values (".$iID.",'".pg_escape_string($sLang)."','".pg_escape_string($sLabel)."');\n";
+ }
+
+ foreach($aArticle['description'] as $sLang => $sLabel) {
+ fputcsv($hFileEntityDescription, array($iID,$sLang,$sLabel));
+ // echo "insert into entity_description values (".$iID.",'".pg_escape_string($sLang)."','".pg_escape_string($sLabel)."');\n";
+ }
+
+ foreach($aArticle['aliases'] as $sLang => $aLabels) {
+ $aUniqueAlias = array();
+ foreach($aLabels as $sLabel) {
+ if (!isset($aUniqueAlias[$sLabel]) && $sLabel) {
+ fputcsv($hFileEntityAlias, array($iID,$sLang,$sLabel));
+ // echo "insert into entity_alias values (".$iID.",'".pg_escape_string($sLang)."','".pg_escape_string($sLabel)."');\n";
+ $aUniqueAlias[$sLabel] = true;
+ }
+ }
+ }
+
+ foreach($aArticle['links'] as $sLang => $sLabel) {
+ fputcsv($hFileEntityLink, array($iID,$sLang,$sLabel));
+ // echo "insert into entity_link values (".$iID.",'".pg_escape_string($sLang)."','".pg_escape_string($sLabel)."');\n";
+ }
+
+
+ if (isset($aArticle['claims'])) {
+
+ foreach($aArticle['claims'] as $iClaim => $aClaim) {
+
+ $bFail = false;
+ if ($aClaim['m'][0] == 'novalue') continue;
+ if ($aClaim['m'][0] == 'somevalue') continue;
+ $iPID = (int)$aClaim['m'][1];
+ if ($aClaim['m'][0] != 'value') $bFail = true;
+ if ($aClaim['m'][2]== 'wikibase-entityid') {
+
+ if ($aClaim['m'][3]['entity-type'] != 'item') $bFail = true;
+ fputcsv($hFileEntityProperty, array($iID,$iClaim,$iPID,null,$aClaim['m'][3]['numeric-id'],null,null));
+ // echo "insert into entity_property values (nextval('seq_entity_property'),".$iID.",".$iPID.",null,".$aClaim['m'][3]['numeric-id'].",null);\n";
+
+ } elseif ($aClaim['m'][2] == 'globecoordinate') {
+
+ if ($aClaim['m'][3]['globe'] != 'http://www.wikidata.org/entity/Q2') $bFail = true;
+ fputcsv($hFileEntityProperty, array($iID,$iClaim,$iPID,null,null,"SRID=4326;POINT(".((float)$aClaim['m'][3]['longitude'])." ".((float)$aClaim['m'][3]['latitude']).")",null));
+ // echo "insert into entity_property values (nextval('seq_entity_property'),".$iID.",".$iPID.",null,null,ST_SetSRID(ST_MakePoint(".((float)$aClaim['m'][3]['longitude']).", ".((float)$aClaim['m'][3]['latitude'])."),4326));\n";
+
+ } elseif ($aClaim['m'][2] == 'time') {
+ // TODO!
+/*
+ if ($aClaim['m'][3]['calendarmodel'] == 'http://www.wikidata.org/entity/Q1985727') {
+ // Gregorian
+ if (preg_match('#(\\+|-)0*([0-9]{4})-([0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2})Z#', $aClaim['m'][3]['time'], $aMatch)) {
+ if ((int)$aMatch[2] < 4700 && ) {
+ $sDateString = $aMatch[2].'-'.$aMatch[3].($aClaim['m'][3]['timezone']>=0?'+':'').$aClaim['m'][3]['timezone'].($aMatch[1]=='-'?' bc':'');
+ fputcsv($hFileEntityProperty, array($iID,$iClaim,$iPID,null,null,null,$sDateString));
+ }
+ } else {
+// $bFail = true;
+ }
+ } elseif ( $aClaim['m'][3]['calendarmodel'] != 'http://www.wikidata.org/entity/Q1985786') {
+/ *
+ // Julian
+ if (preg_match('#(\\+|-)0*([0-9]{4})-([0-9]{2})-([0-9]{2})T([0-9]{2}:[0-9]{2}:[0-9]{2})Z#', $aClaim['m'][3]['time'], $aMatch)) {
+var_dump($aMatch);
+exit;
+$iDayCount = juliantojd(2, 11, 1732);
+var_dump($iDayCount, jdtogregorian($iDayCount));
+ } else {
+ $bFail = true;
+exit;
+ }
+exit;
+* /
+ } else {
+// $bFail = true;
+ }
+*/
+ } elseif ($aClaim['m'][2] == 'string') {
+
+ // echo "insert into entity_property values (nextval('seq_entity_property'),".$iID.",".$iPID.",'".pg_escape_string($aClaim['m'][3])."',null,null);\n";
+ fputcsv($hFileEntityProperty, array($iID,$iClaim,$iPID,$aClaim['m'][3],null,null,null));
+
+ } else {
+
+ $bFail = true;
+
+ }
+
+ // Don't care about sources: if ($aClaim['refs'] != array()) $bFail = true;
+
+ if ($bFail) {
+ var_dump($sTitle);
+ var_dump($aClaim);
+ } else {
+ // process
+ }
+
+ }
+
+ }
+ }
+ }
+ fclose($hFile);
+ fclose($hFileEntity);
+ fclose($hFileEntityLabel);
+ fclose($hFileEntityDescription);
+ fclose($hFileEntityAlias);
+ fclose($hFileEntityLink);
+ fclose($hFileEntityProperty);
+}
--- /dev/null
+<?php
+
+ for($iTimestamp = mktime(0, 0, 0, 5, 1, 2013); $iTimestamp < mktime(0, 0, 0, 6, 15, 2013); $iTimestamp += 24*60*60)
+ {
+ $sYear = date("Y", $iTimestamp);
+ $sMonth = date("Y-m", $iTimestamp);
+ $sDay = date("Ymd", $iTimestamp);
+
+ for($iHour = 0; $iHour < 24; $iHour++)
+ {
+ $sFilename = sprintf("pagecounts-".$sDay."-%02d0000", $iHour);
+ echo $sFilename."\n";
+ if (!file_exists($sFilename.'.gz'))
+ {
+ exec('wget http://dumps.wikimedia.org/other/pagecounts-raw/'.$sYear.'/'.$sMonth.'/'.$sFilename.'.gz');
+ }
+
+ exec('gzip -dc '.$sFilename.'.gz'.' | grep -e "^[a-z]\{2\} [^ :]\+ [0-9]\+" > hour.txt');
+
+ $hPrevTotals = @fopen("totals.txt", "r");
+ $hDayTotals = @fopen("hour.txt", "r");
+ $hNewTotals = @fopen("newtotals.txt", "w");
+
+ $sPrevKey = $sDayKey = true;
+ $sPrevLine = true;
+ $sDayLine = true;
+
+ do
+ {
+ if ($sPrevKey === $sDayKey)
+ {
+ if ($sPrevLine !== true) fputs($hNewTotals, "$sPrevKey ".($iPrevValue+$iDayValue)."\n");
+ $sPrevLine = true;
+ $sDayLine = true;
+ }
+ else if ($sDayKey !== false && ($sPrevKey > $sDayKey || $sPrevKey === false))
+ {
+ fputs($hNewTotals, "$sDayKey ".($iDayValue)."\n");
+ $sDayLine = true;
+ }
+ else if ($sPrevKey !== false && ($sDayKey > $sPrevKey || $sDayKey === false))
+ {
+ fputs($hNewTotals, "$sPrevKey ".($iPrevValue)."\n");
+ $sPrevLine = true;
+ }
+
+ if ($sPrevLine === true)
+ {
+ $sPrevLine = $hPrevTotals?fgets($hPrevTotals, 4096):false;
+ if ($sPrevLine !== false)
+ {
+ $aPrevLine = explode(' ', $sPrevLine);
+ $sPrevKey = $aPrevLine[0].' '.$aPrevLine[1];
+ $iPrevValue = (int)$aPrevLine[2];
+ }
+ else
+ {
+ $sPrevKey = false;
+ $iPrevValue = 0;
+ }
+ }
+
+ if ($sDayLine === true)
+ {
+ $sDayLine = $hDayTotals?fgets($hDayTotals, 4096):false;
+ if ($sDayLine !== false)
+ {
+ preg_match('#^([a-z]{2}) ([^ :]+) ([0-9]+) [0-9]+$#', $sDayLine, $aMatch);
+ $sDayKey = $aMatch[1].' '.$aMatch[2];
+ $iDayValue = (int)$aMatch[3];
+ }
+ else
+ {
+ $sDayKey = false;
+ $iDayValue = 0;
+ }
+ }
+
+ } while ($sPrevLine !== false || $sDayLine !== false);
+
+ @fclose($hPrevTotals);
+ @fclose($hDayTotals);
+ @fclose($hNewTotals);
+
+ @unlink("totals.txt");
+ rename("newtotals.txt", "totals.txt");
+ }
+ }
+
+// Notes:
+/*
+ gzip -dc $FILE.gz | grep -e "^en [^ :]\+ [0-9]\+" | sed "s#\(^[a-z]\{2\}\) \([^ :]\+\) \([0-9]\+\) [0-9]\+#update wikipedia_article set hit_count = coalesce(hit_count,0) + \3 where language = '\1' and title = catch_decode_url_part('\2');#g" | /opt/mapquest/stdbase-dev$
+ cat totals.txt | sed "s#\(^[a-z]\{2\}\) \([^ ]\+\) \([0-9]\+\)\$#update entity_link set hits = s,0) + \3 where target = '\1wiki' and value = catch_decode_url_part('\2');#g"
+ cat totals.txt | sed "s#\(^[a-z]\{2\}\) \([^ ]\+\) \([0-9]\+\)\$#update entity_link set hits = coalesce(hits,0) + \3 where target = '\1wiki' and value = catch_decode_url_part('\2');#g"
+*/