diff --git a/lib/Geocode.php b/lib/Geocode.php index 58bc3bfc..a6baa96b 100644 --- a/lib/Geocode.php +++ b/lib/Geocode.php @@ -2,10 +2,10 @@ namespace Nominatim; -require_once(CONST_BasePath.'/lib/NearPoint.php'); require_once(CONST_BasePath.'/lib/PlaceLookup.php'); require_once(CONST_BasePath.'/lib/ReverseGeocode.php'); require_once(CONST_BasePath.'/lib/SearchDescription.php'); +require_once(CONST_BasePath.'/lib/SearchContext.php'); class Geocode { @@ -888,6 +888,8 @@ class Geocode { if (!$this->sQuery && !$this->aStructuredQuery) return array(); + $oCtx = new SearchContext(); + $sNormQuery = $this->normTerm($this->sQuery); $sLanguagePrefArraySQL = getArraySQL( array_map("getDBQuoted", $this->aLangPrefOrder) @@ -926,20 +928,12 @@ class Geocode } // Do we have anything that looks like a lat/lon pair? - $oNearPoint = false; - if ($aLooksLike = NearPoint::extractFromQuery($sQuery)) { - $oNearPoint = $aLooksLike['pt']; - $sQuery = $aLooksLike['query']; - } + $sQuery = $oCtx->setNearPointFromQuery($sQuery); $aSearchResults = array(); if ($sQuery || $this->aStructuredQuery) { // Start with a single blank search - $aSearches = array(new SearchDescription()); - - if ($oNearPoint) { - $aSearches[0]->setNear($oNearPoint); - } + $aSearches = array(new SearchDescription($oCtx)); if ($sQuery) { $sQuery = $aSearches[0]->extractKeyValuePairs($sQuery); @@ -1166,7 +1160,7 @@ class Geocode } } elseif (!$oSearch->isNamedSearch()) { // looking for a POI in a geographic area - if (!$bBoundingBoxSearch && !$oSearch->isNearSearch()) { + if (!$bBoundingBoxSearch && !$oCtx->hasNearPoint()) { continue; } @@ -1319,11 +1313,7 @@ class Geocode $oReverse = new ReverseGeocode($this->oDB); $oReverse->setZoom(18); - $aLookup = $oReverse->lookup( - $oNearPoint->lat(), - $oNearPoint->lon(), - false - ); + $aLookup = $oReverse->lookupPoint($oCtx->sqlNear, false); if (CONST_Debug) var_dump("Reverse search", $aLookup); diff --git a/lib/NearPoint.php b/lib/NearPoint.php deleted file mode 100644 index 30845b76..00000000 --- a/lib/NearPoint.php +++ /dev/null @@ -1,158 +0,0 @@ -fLat = (float)$lat; - $this->fLon = (float)$lon; - $this->fRadius = (float)$radius; - $this->sSQL = 'ST_SetSRID(ST_Point('.$this->fLon.','.$this->fLat.'),4326)'; - } - - public function lat() - { - return $this->fLat; - } - - public function lon() - { - return $this->fLon; - } - - public function radius() - { - return $this->fRadius; - } - - public function distanceSQL($sObj) - { - return 'ST_Distance('.$this->sSQL.", $sObj)"; - } - - public function withinSQL($sObj) - { - return sprintf('ST_DWithin(%s, %s, %F)', $sObj, $this->sSQL, $this->fRadius); - } - - /** - * Check that the coordinates are valid WSG84 coordinates. - * - * @return bool True if the coordinates are correctly bounded. - */ - public function isValid() - { - return ($this->fLat <= 90.1 - && $this->fLat >= -90.1 - && $this->fLon <= 180.1 - && $this->fLon >= -180.1); - } - - /** - * Extract a coordinate point from a query string. - * - * If a coordinate is found an array of a new NearPoint and the - * remaining query is returned or false otherwise. - * - * @param string $sQuery Query to scan. - * - * @return array|false If a coordinate was found, an array with - * `pt` as the NearPoint coordinates and `query` - * with the remaining query string. False otherwiese. - */ - public static function extractFromQuery($sQuery) - { - // Do we have anything that looks like a lat/lon pair? - // returns array(lat,lon,query_with_lat_lon_removed) - // or null - $sFound = null; - $fQueryLat = null; - $fQueryLon = null; - - if (preg_match('/\\s*([NS])[ ]+([0-9]+[0-9.]*)[° ]+([0-9.]+)?[′\']*[, ]+([EW])[ ]+([0-9]+)[° ]+([0-9]+[0-9.]*)[′\']*\\s*/', $sQuery, $aData)) { - /* 1 2 3 4 5 6 - * degrees decimal minutes - * N 40 26.767, W 79 58.933 - * N 40°26.767′, W 79°58.933′ - */ - $sFound = $aData[0]; - $fQueryLat = ($aData[1]=='N'?1:-1) * ($aData[2] + $aData[3]/60); - $fQueryLon = ($aData[4]=='E'?1:-1) * ($aData[5] + $aData[6]/60); - } elseif (preg_match('/\\s*([0-9]+)[° ]+([0-9]+[0-9.]*)?[′\']*[ ]+([NS])[, ]+([0-9]+)[° ]+([0-9]+[0-9.]*)?[′\' ]+([EW])\\s*/', $sQuery, $aData)) { - /* 1 2 3 4 5 6 - * degrees decimal minutes - * 40 26.767 N, 79 58.933 W - * 40° 26.767′ N 79° 58.933′ W - */ - $sFound = $aData[0]; - $fQueryLat = ($aData[3]=='N'?1:-1) * ($aData[1] + $aData[2]/60); - $fQueryLon = ($aData[6]=='E'?1:-1) * ($aData[4] + $aData[5]/60); - } elseif (preg_match('/\\s*([NS])[ ]([0-9]+)[° ]+([0-9]+)[′\' ]+([0-9]+)[″"]*[, ]+([EW])[ ]([0-9]+)[° ]+([0-9]+)[′\' ]+([0-9]+)[″"]*\\s*/', $sQuery, $aData)) { - /* 1 2 3 4 5 6 7 8 - * degrees decimal seconds - * N 40 26 46 W 79 58 56 - * N 40° 26′ 46″, W 79° 58′ 56″ - */ - $sFound = $aData[0]; - $fQueryLat = ($aData[1]=='N'?1:-1) * ($aData[2] + $aData[3]/60 + $aData[4]/3600); - $fQueryLon = ($aData[5]=='E'?1:-1) * ($aData[6] + $aData[7]/60 + $aData[8]/3600); - } elseif (preg_match('/\\s*([0-9]+)[° ]+([0-9]+)[′\' ]+([0-9]+)[″" ]+([NS])[, ]+([0-9]+)[° ]+([0-9]+)[′\' ]+([0-9]+)[″" ]+([EW])\\s*/', $sQuery, $aData)) { - /* 1 2 3 4 5 6 7 8 - * degrees decimal seconds - * 40 26 46 N 79 58 56 W - * 40° 26′ 46″ N, 79° 58′ 56″ W - */ - $sFound = $aData[0]; - $fQueryLat = ($aData[4]=='N'?1:-1) * ($aData[1] + $aData[2]/60 + $aData[3]/3600); - $fQueryLon = ($aData[8]=='E'?1:-1) * ($aData[5] + $aData[6]/60 + $aData[7]/3600); - } elseif (preg_match('/\\s*([NS])[ ]([0-9]+[0-9]*\\.[0-9]+)[°]*[, ]+([EW])[ ]([0-9]+[0-9]*\\.[0-9]+)[°]*\\s*/', $sQuery, $aData)) { - /* 1 2 3 4 - * degrees decimal - * N 40.446° W 79.982° - */ - $sFound = $aData[0]; - $fQueryLat = ($aData[1]=='N'?1:-1) * ($aData[2]); - $fQueryLon = ($aData[3]=='E'?1:-1) * ($aData[4]); - } elseif (preg_match('/\\s*([0-9]+[0-9]*\\.[0-9]+)[° ]+([NS])[, ]+([0-9]+[0-9]*\\.[0-9]+)[° ]+([EW])\\s*/', $sQuery, $aData)) { - /* 1 2 3 4 - * degrees decimal - * 40.446° N 79.982° W - */ - $sFound = $aData[0]; - $fQueryLat = ($aData[2]=='N'?1:-1) * ($aData[1]); - $fQueryLon = ($aData[4]=='E'?1:-1) * ($aData[3]); - } elseif (preg_match('/(\\s*\\[|^\\s*|\\s*)(-?[0-9]+[0-9]*\\.[0-9]+)[, ]+(-?[0-9]+[0-9]*\\.[0-9]+)(\\]\\s*|\\s*$|\\s*)/', $sQuery, $aData)) { - /* 1 2 3 4 - * degrees decimal - * 12.34, 56.78 - * 12.34 56.78 - * [12.456,-78.90] - */ - $sFound = $aData[0]; - $fQueryLat = $aData[2]; - $fQueryLon = $aData[3]; - } else { - return false; - } - - $oPt = new NearPoint($fQueryLat, $fQueryLon); - - if (!$oPt->isValid()) return false; - - $sQuery = trim(str_replace($sFound, ' ', $sQuery)); - - return array('pt' => $oPt, 'query' => $sQuery); - } -} diff --git a/lib/ReverseGeocode.php b/lib/ReverseGeocode.php index 1de0893c..9b43a3e3 100644 --- a/lib/ReverseGeocode.php +++ b/lib/ReverseGeocode.php @@ -66,15 +66,22 @@ class ReverseGeocode ); } + public function lookup($fLat, $fLon, $bDoInterpolation = true) + { + return $this->lookupPoint( + 'ST_SetSRID(ST_Point('.$fLon.','.$fLat.'),4326)', + $bDoInterpolation + ); + } + /* lookup() * returns { place_id =>, type => '(osm|tiger)' } * fails if no place was found */ - public function lookup($fLat, $fLon, $bDoInterpolation = true) + public function lookupPoint($sPointSQL, $bDoInterpolation = true) { - $sPointSQL = 'ST_SetSRID(ST_Point('.$fLon.','.$fLat.'),4326)'; $iMaxRank = $this->iMaxRank; // Find the nearest point diff --git a/lib/SearchContext.php b/lib/SearchContext.php new file mode 100644 index 00000000..a6b63586 --- /dev/null +++ b/lib/SearchContext.php @@ -0,0 +1,73 @@ +fNearRadius !== false; + } + + public function nearRadius() + { + return $this->fNearRadius; + } + + public function setNearPoint($fLat, $fLon, $fRadius = 0.1) + { + $this->fNearRadius = $fRadius; + $this->sqlNear = 'ST_SetSRID(ST_Point('.$fLon.','.$fLat.'),4326)'; + } + + /** + * Extract a coordinate point from a query string. + * + * @param string $sQuery Query to scan. + * + * @return The remaining query string. + */ + public function setNearPointFromQuery($sQuery) + { + $aResult = parseLatLon($sQuery); + + if ($aResult !== false + && $aResult[1] <= 90.1 + && $aResult[1] >= -90.1 + && $aResult[2] <= 180.1 + && $aResult[2] >= -180.1 + ) { + $this->setNearPoint($aResult[1], $aResult[2]); + $sQuery = trim(str_replace($aResult[0], ' ', $sQuery)); + } + + return $sQuery; + } + + public function distanceSQL($sObj) + { + return 'ST_Distance('.$this->sqlNear.", $sObj)"; + } + + public function withinSQL($sObj) + { + return sprintf('ST_DWithin(%s, %s, %F)', $sObj, $this->sqlNear, $this->fNearRadius); + } +} diff --git a/lib/SearchDescription.php b/lib/SearchDescription.php index 42e5af30..7073186b 100644 --- a/lib/SearchDescription.php +++ b/lib/SearchDescription.php @@ -3,6 +3,7 @@ namespace Nominatim; require_once(CONST_BasePath.'/lib/SpecialSearchOperator.php'); +require_once(CONST_BasePath.'/lib/SearchContext.php'); /** * Description of a single interpretation of a search query. @@ -33,15 +34,20 @@ class SearchDescription private $sHouseNumber = ''; /// Postcode for the object. private $sPostcode = ''; - /// Geographic search area. - private $oNearPoint = false; + /// Global search constraints. + private $oContext; // Temporary values used while creating the search description. - /// Index of phrase currently processed + /// Index of phrase currently processed. private $iNamePhrase = -1; + public function __construct($oContext) + { + $this->oContext = $oContext; + } + public function getRank() { return $this->iSearchRank; @@ -58,11 +64,6 @@ class SearchDescription return $this->sPostcode; } - public function setNear(&$oNearPoint) - { - $this->oNearPoint = $oNearPoint; - } - public function setPoiSearch($iOperator, $sClass, $sType) { $this->iOperator = $iOperator; @@ -78,12 +79,7 @@ class SearchDescription public function isCountrySearch() { return $this->sCountryCode && sizeof($this->aName) == 0 - && !$this->iOperator && !$this->oNearPoint; - } - - public function isNearSearch() - { - return (bool) $this->oNearPoint; + && !$this->iOperator && !$this->oContext->hasNearPoint(); } public function isPoiSearch() @@ -400,8 +396,8 @@ class SearchDescription if ($sCountryList) { $sSQL .= ' JOIN placex USING (place_id)'; } - if ($this->oNearPoint) { - $sSQL .= ' WHERE '.$this->oNearPoint->withinSQL('ct.centroid'); + if ($this->oContext->hasNearPoint()) { + $sSQL .= ' WHERE '.$this->oContext->withinSQL('ct.centroid'); } else { $sSQL .= " WHERE ST_Contains($sViewboxSQL, ct.centroid)"; } @@ -413,23 +409,23 @@ class SearchDescription } if ($sViewboxCentreSQL) { $sSQL .= " ORDER BY ST_Distance($sViewboxCentreSQL, ct.centroid) ASC"; - } elseif ($this->oNearPoint) { - $sSQL .= ' ORDER BY '.$this->oNearPoint->distanceSQL('ct.centroid').' ASC'; + } elseif ($this->oContext->hasNearPoint()) { + $sSQL .= ' ORDER BY '.$this->oContext->distanceSQL('ct.centroid').' ASC'; } $sSQL .= " limit $iLimit"; if (CONST_Debug) var_dump($sSQL); return chksql($oDB->getCol($sSQL)); } - if ($this->oNearPoint) { + if ($this->oContext->hasNearPoint()) { $sSQL = 'SELECT place_id FROM placex WHERE '; $sSQL .= 'class=\''.$this->sClass."' and type='".$this->sType."'"; - $sSQL .= ' AND '.$this->oNearPoint->withinSQL('geometry'); + $sSQL .= ' AND '.$this->oContext->withinSQL('geometry'); $sSQL .= ' AND linked_place_id is null'; if ($sCountryList) { $sSQL .= " AND country_code in ($sCountryList)"; } - $sSQL .= ' ORDER BY '.$this->oNearPoint->distanceSQL('centroid')." ASC"; + $sSQL .= ' ORDER BY '.$this->oContext->distanceSQL('centroid')." ASC"; $sSQL .= " LIMIT $iLimit"; if (CONST_Debug) var_dump($sSQL); return chksql($oDB->getCol($sSQL)); @@ -526,9 +522,9 @@ class SearchDescription } } - if ($this->oNearPoint) { - $aTerms[] = $this->oNearPoint->withinSQL('centroid'); - $aOrder[] = $this->oNearPoint->distanceSQL('centroid'); + if ($this->oContext->hasNearPoint()) { + $aTerms[] = $this->oContext->withinSQL('centroid'); + $aOrder[] = $this->oContext->distanceSQL('centroid'); } elseif ($this->sPostcode) { if (!sizeof($this->aAddress)) { $aTerms[] = "EXISTS(SELECT place_id FROM location_postcode p WHERE p.postcode = '".$this->sPostcode."' AND ST_DWithin(search_name.centroid, p.geometry, 0.1))"; @@ -545,8 +541,8 @@ class SearchDescription $aTerms[] = 'centroid && '.$sViewboxSmall; } - if ($this->oNearPoint) { - $aOrder[] = $this->oNearPoint->distanceSQL('centroid'); + if ($this->oContext->hasNearPoint()) { + $aOrder[] = $this->oContext->distanceSQL('centroid'); } if ($this->sHouseNumber) { @@ -765,8 +761,8 @@ class SearchDescription $fRange = 0.05; $sOrderBySQL = ''; - if ($this->oNearPoint) { - $sOrderBySQL = $this->oNearPoint->distanceSQL('l.centroid'); + if ($this->oContext->hasNearPoint()) { + $sOrderBySQL = $this->oContext->distanceSQL('l.centroid'); } elseif ($sPlaceIDs) { $sOrderBySQL = "ST_Distance(l.centroid, f.geometry)"; } elseif ($sPlaceGeom) { @@ -804,13 +800,13 @@ class SearchDescription $aClassPlaceIDs = array_merge($aClassPlaceIDs, chksql($oDB->getCol($sSQL))); } else { - if ($this->oNearPoint) { - $fRange = $this->oNearPoint->radius(); + if ($this->oContext->hasNearPoint()) { + $fRange = $this->oContext->nearRadius(); } $sOrderBySQL = ''; - if ($this->oNearPoint) { - $sOrderBySQL = $this->oNearPoint->distanceSQL('l.geometry'); + if ($this->oContext->hasNearPoint()) { + $sOrderBySQL = $this->oContext->distanceSQL('l.geometry'); } else { $sOrderBySQL = "ST_Distance(l.geometry, f.geometry)"; } @@ -878,14 +874,6 @@ class SearchDescription echo "".$this->sPostcode.""; echo "".$this->sHouseNumber.""; - if ($this->oNearPoint) { - echo "".$this->oNearPoint->lat().""; - echo "".$this->oNearPoint->lon().""; - echo "".$this->oNearPoint->radius().""; - } else { - echo ""; - } - echo ""; } } diff --git a/lib/lib.php b/lib/lib.php index 969d58a3..b5fbee3e 100644 --- a/lib/lib.php +++ b/lib/lib.php @@ -489,9 +489,8 @@ function _debugDumpGroupedSearches($aData, $aTokens) } echo ""; echo ""; - echo ""; - echo ""; - echo ""; + echo ""; + echo ""; foreach ($aData as $iRank => $aRankedSet) { foreach ($aRankedSet as $aRow) { $aRow->dumpAsHtmlTableRow($aWordsIDs); @@ -546,6 +545,81 @@ function addQuotes($s) return "'".$s."'"; } +function parseLatLon($sQuery) +{ + $sFound = null; + $fQueryLat = null; + $fQueryLon = null; + + if (preg_match('/\\s*([NS])[ ]+([0-9]+[0-9.]*)[° ]+([0-9.]+)?[′\']*[, ]+([EW])[ ]+([0-9]+)[° ]+([0-9]+[0-9.]*)[′\']*\\s*/', $sQuery, $aData)) { + /* 1 2 3 4 5 6 + * degrees decimal minutes + * N 40 26.767, W 79 58.933 + * N 40°26.767′, W 79°58.933′ + */ + $sFound = $aData[0]; + $fQueryLat = ($aData[1]=='N'?1:-1) * ($aData[2] + $aData[3]/60); + $fQueryLon = ($aData[4]=='E'?1:-1) * ($aData[5] + $aData[6]/60); + } elseif (preg_match('/\\s*([0-9]+)[° ]+([0-9]+[0-9.]*)?[′\']*[ ]+([NS])[, ]+([0-9]+)[° ]+([0-9]+[0-9.]*)?[′\' ]+([EW])\\s*/', $sQuery, $aData)) { + /* 1 2 3 4 5 6 + * degrees decimal minutes + * 40 26.767 N, 79 58.933 W + * 40° 26.767′ N 79° 58.933′ W + */ + $sFound = $aData[0]; + $fQueryLat = ($aData[3]=='N'?1:-1) * ($aData[1] + $aData[2]/60); + $fQueryLon = ($aData[6]=='E'?1:-1) * ($aData[4] + $aData[5]/60); + } elseif (preg_match('/\\s*([NS])[ ]([0-9]+)[° ]+([0-9]+)[′\' ]+([0-9]+)[″"]*[, ]+([EW])[ ]([0-9]+)[° ]+([0-9]+)[′\' ]+([0-9]+)[″"]*\\s*/', $sQuery, $aData)) { + /* 1 2 3 4 5 6 7 8 + * degrees decimal seconds + * N 40 26 46 W 79 58 56 + * N 40° 26′ 46″, W 79° 58′ 56″ + */ + $sFound = $aData[0]; + $fQueryLat = ($aData[1]=='N'?1:-1) * ($aData[2] + $aData[3]/60 + $aData[4]/3600); + $fQueryLon = ($aData[5]=='E'?1:-1) * ($aData[6] + $aData[7]/60 + $aData[8]/3600); + } elseif (preg_match('/\\s*([0-9]+)[° ]+([0-9]+)[′\' ]+([0-9]+)[″" ]+([NS])[, ]+([0-9]+)[° ]+([0-9]+)[′\' ]+([0-9]+)[″" ]+([EW])\\s*/', $sQuery, $aData)) { + /* 1 2 3 4 5 6 7 8 + * degrees decimal seconds + * 40 26 46 N 79 58 56 W + * 40° 26′ 46″ N, 79° 58′ 56″ W + */ + $sFound = $aData[0]; + $fQueryLat = ($aData[4]=='N'?1:-1) * ($aData[1] + $aData[2]/60 + $aData[3]/3600); + $fQueryLon = ($aData[8]=='E'?1:-1) * ($aData[5] + $aData[6]/60 + $aData[7]/3600); + } elseif (preg_match('/\\s*([NS])[ ]([0-9]+[0-9]*\\.[0-9]+)[°]*[, ]+([EW])[ ]([0-9]+[0-9]*\\.[0-9]+)[°]*\\s*/', $sQuery, $aData)) { + /* 1 2 3 4 + * degrees decimal + * N 40.446° W 79.982° + */ + $sFound = $aData[0]; + $fQueryLat = ($aData[1]=='N'?1:-1) * ($aData[2]); + $fQueryLon = ($aData[3]=='E'?1:-1) * ($aData[4]); + } elseif (preg_match('/\\s*([0-9]+[0-9]*\\.[0-9]+)[° ]+([NS])[, ]+([0-9]+[0-9]*\\.[0-9]+)[° ]+([EW])\\s*/', $sQuery, $aData)) { + /* 1 2 3 4 + * degrees decimal + * 40.446° N 79.982° W + */ + $sFound = $aData[0]; + $fQueryLat = ($aData[2]=='N'?1:-1) * ($aData[1]); + $fQueryLon = ($aData[4]=='E'?1:-1) * ($aData[3]); + } elseif (preg_match('/(\\s*\\[|^\\s*|\\s*)(-?[0-9]+[0-9]*\\.[0-9]+)[, ]+(-?[0-9]+[0-9]*\\.[0-9]+)(\\]\\s*|\\s*$|\\s*)/', $sQuery, $aData)) { + /* 1 2 3 4 + * degrees decimal + * 12.34, 56.78 + * 12.34 56.78 + * [12.456,-78.90] + */ + $sFound = $aData[0]; + $fQueryLat = $aData[2]; + $fQueryLon = $aData[3]; + } else { + return false; + } + + return array($sFound, $fQueryLat, $fQueryLon); +} + function geometryText2Points($geometry_as_text, $fRadius) {
rankName TokensName NotAddress TokensAddress Notcountryoperatorclasstypepostcodehouse#LatLonRadius
Address TokensAddress Notcountryoperatorclasstypepostcodehousenumber