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
{
{
if (!$this->sQuery && !$this->aStructuredQuery) return array();
+ $oCtx = new SearchContext();
+
$sNormQuery = $this->normTerm($this->sQuery);
$sLanguagePrefArraySQL = getArraySQL(
array_map("getDBQuoted", $this->aLangPrefOrder)
}
// 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);
}
} elseif (!$oSearch->isNamedSearch()) {
// looking for a POI in a geographic area
- if (!$bBoundingBoxSearch && !$oSearch->isNearSearch()) {
+ if (!$bBoundingBoxSearch && !$oCtx->hasNearPoint()) {
continue;
}
$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);
+++ /dev/null
-<?php
-
-namespace Nominatim;
-
-/**
- * A geographic point with a search radius.
- */
-class NearPoint
-{
- private $fLat;
- private $fLon;
- private $fRadius;
-
- private $sSQL;
-
-
- public function __construct($lat, $lon, $radius = 0.1)
- {
- $this->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);
- }
-}
);
}
+ 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
--- /dev/null
+<?php
+
+namespace Nominatim;
+
+require_once(CONST_BasePath.'/lib/lib.php');
+
+
+/**
+ * Collects search constraints that are independent of the
+ * actual interpretation of the search query.
+ *
+ * The search context is shared between all SearchDescriptions. This
+ * object mainly serves as context provider for the database queries.
+ * Therefore most data is directly cached as SQL statements.
+ */
+class SearchContext
+{
+ private $fNearRadius = false;
+
+ // cached SQL
+
+ public $sqlNear = '';
+
+ public function hasNearPoint()
+ {
+ return $this->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);
+ }
+}
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.
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;
return $this->sPostcode;
}
- public function setNear(&$oNearPoint)
- {
- $this->oNearPoint = $oNearPoint;
- }
-
public function setPoiSearch($iOperator, $sClass, $sType)
{
$this->iOperator = $iOperator;
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()
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)";
}
}
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));
}
}
- 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))";
$aTerms[] = 'centroid && '.$sViewboxSmall;
}
- if ($this->oNearPoint) {
- $aOrder[] = $this->oNearPoint->distanceSQL('centroid');
+ if ($this->oContext->hasNearPoint()) {
+ $aOrder[] = $this->oContext->distanceSQL('centroid');
}
if ($this->sHouseNumber) {
$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) {
$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)";
}
echo "<td>".$this->sPostcode."</td>";
echo "<td>".$this->sHouseNumber."</td>";
- if ($this->oNearPoint) {
- echo "<td>".$this->oNearPoint->lat()."</td>";
- echo "<td>".$this->oNearPoint->lon()."</td>";
- echo "<td>".$this->oNearPoint->radius()."</td>";
- } else {
- echo "<td></td><td></td><td></td>";
- }
-
echo "</tr>";
}
}
}
echo "<table border=\"1\">";
echo "<tr><th>rank</th><th>Name Tokens</th><th>Name Not</th>";
- echo "<th>Address Tokens</th><th>Address Not</th><th>country</th>";
- echo "<th>operator</th><th>class</th><th>type</th><th>postcode</th><th>house#</th>";
- echo "<th>Lat</th><th>Lon</th><th>Radius</th></tr>";
+ echo "<th>Address Tokens</th><th>Address Not</th><th>country</th><th>operator</th>";
+ echo "<th>class</th><th>type</th><th>postcode</th><th>housenumber</th></tr>";
foreach ($aData as $iRank => $aRankedSet) {
foreach ($aRankedSet as $aRow) {
$aRow->dumpAsHtmlTableRow($aWordsIDs);
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)
{