3  * SPDX-License-Identifier: GPL-2.0-only
 
   5  * This file is part of Nominatim. (https://nominatim.org)
 
   7  * Copyright (C) 2022 by the Nominatim developer community.
 
   8  * For a full list of authors see the git log.
 
  13 require_once(CONST_LibDir.'/lib.php');
 
  17  * Collection of search constraints that are independent of the
 
  18  * actual interpretation of the search query.
 
  20  * The search context is shared between all SearchDescriptions. This
 
  21  * object mainly serves as context provider for the database queries.
 
  22  * Therefore most data is directly cached as SQL statements.
 
  26     /// Search radius around a given Near reference point.
 
  27     private $fNearRadius = false;
 
  28     /// True if search must be restricted to viewbox only.
 
  29     public $bViewboxBounded = false;
 
  31     /// Reference point for search (as SQL).
 
  33     /// Viewbox selected for search (as SQL).
 
  34     public $sqlViewboxSmall = '';
 
  35     /// Viewbox with a larger buffer around (as SQL).
 
  36     public $sqlViewboxLarge = '';
 
  37     /// Reference along a route (as SQL).
 
  38     public $sqlViewboxCentre = '';
 
  39     /// List of countries to restrict search to (as array).
 
  40     public $aCountryList = null;
 
  41     /// List of countries to restrict search to (as SQL).
 
  42     public $sqlCountryList = '';
 
  43     /// List of place IDs to exclude (as SQL).
 
  44     private $sqlExcludeList = '';
 
  45     /// Subset of word ids of full words in the query.
 
  46     private $aFullNameWords = array();
 
  48     public function setFullNameWords($aWordList)
 
  50         $this->aFullNameWords = $aWordList;
 
  53     public function getFullNameTerms()
 
  55         return $this->aFullNameWords;
 
  59      * Check if a reference point is defined.
 
  61      * @return bool True if a reference point is defined.
 
  63     public function hasNearPoint()
 
  65         return $this->fNearRadius !== false;
 
  69      * Get radius around reference point.
 
  71      * @return float Search radius around reference point.
 
  73     public function nearRadius()
 
  75         return $this->fNearRadius;
 
  79      * Set search reference point in WGS84.
 
  81      * If set, then only places around this point will be taken into account.
 
  83      * @param float $fLat    Latitude of point.
 
  84      * @param float $fLon    Longitude of point.
 
  85      * @param float $fRadius Search radius around point.
 
  89     public function setNearPoint($fLat, $fLon, $fRadius = 0.1)
 
  91         $this->fNearRadius = $fRadius;
 
  92         $this->sqlNear = 'ST_SetSRID(ST_Point('.$fLon.','.$fLat.'),4326)';
 
  96      * Check if the search is geographically restricted.
 
  98      * Searches are restricted if a reference point is given or if
 
  99      * a bounded viewbox is set.
 
 101      * @return bool True, if the search is geographically bounded.
 
 103     public function isBoundedSearch()
 
 105         return $this->hasNearPoint() || ($this->sqlViewboxSmall && $this->bViewboxBounded);
 
 109      * Set rectangular viewbox.
 
 111      * The viewbox may be bounded which means that no search results
 
 112      * must be outside the viewbox.
 
 114      * @param float[4] $aViewBox Coordinates of the viewbox.
 
 115      * @param bool     $bBounded True if the viewbox is bounded.
 
 119     public function setViewboxFromBox(&$aViewBox, $bBounded)
 
 121         $this->bViewboxBounded = $bBounded;
 
 122         $this->sqlViewboxCentre = '';
 
 124         $this->sqlViewboxSmall = sprintf(
 
 125             'ST_SetSRID(ST_MakeBox2D(ST_Point(%F,%F),ST_Point(%F,%F)),4326)',
 
 132         $fHeight = abs($aViewBox[0] - $aViewBox[2]);
 
 133         $fWidth = abs($aViewBox[1] - $aViewBox[3]);
 
 135         $this->sqlViewboxLarge = sprintf(
 
 136             'ST_SetSRID(ST_MakeBox2D(ST_Point(%F,%F),ST_Point(%F,%F)),4326)',
 
 137             max($aViewBox[0], $aViewBox[2]) + $fHeight,
 
 138             max($aViewBox[1], $aViewBox[3]) + $fWidth,
 
 139             min($aViewBox[0], $aViewBox[2]) - $fHeight,
 
 140             min($aViewBox[1], $aViewBox[3]) - $fWidth
 
 145      * Set viewbox along a route.
 
 147      * The viewbox may be bounded which means that no search results
 
 148      * must be outside the viewbox.
 
 150      * @param object   $oDB          Nominatim::DB instance to use for computing the box.
 
 151      * @param string[] $aRoutePoints List of x,y coordinates along a route.
 
 152      * @param float    $fRouteWidth  Buffer around the route to use.
 
 153      * @param bool     $bBounded     True if the viewbox bounded.
 
 157     public function setViewboxFromRoute(&$oDB, $aRoutePoints, $fRouteWidth, $bBounded)
 
 159         $this->bViewboxBounded = $bBounded;
 
 160         $this->sqlViewboxCentre = "ST_SetSRID('LINESTRING(";
 
 162         foreach ($aRoutePoints as $aPoint) {
 
 163             $fPoint = (float)$aPoint;
 
 164             $this->sqlViewboxCentre .= $sSep.$fPoint;
 
 165             $sSep = ($sSep == ' ') ? ',' : ' ';
 
 167         $this->sqlViewboxCentre .= ")'::geometry,4326)";
 
 169         $sSQL = 'ST_BUFFER('.$this->sqlViewboxCentre.','.($fRouteWidth/69).')';
 
 170         $sGeom = $oDB->getOne('select '.$sSQL, null, 'Could not get small viewbox');
 
 171         $this->sqlViewboxSmall = "'".$sGeom."'::geometry";
 
 173         $sSQL = 'ST_BUFFER('.$this->sqlViewboxCentre.','.($fRouteWidth/30).')';
 
 174         $sGeom = $oDB->getOne('select '.$sSQL, null, 'Could not get large viewbox');
 
 175         $this->sqlViewboxLarge = "'".$sGeom."'::geometry";
 
 179      * Set list of excluded place IDs.
 
 181      * @param integer[] $aExcluded List of IDs.
 
 185     public function setExcludeList($aExcluded)
 
 187         $this->sqlExcludeList = ' not in ('.join(',', $aExcluded).')';
 
 191      * Set list of countries to restrict search to.
 
 193      * @param string[] $aCountries List of two-letter lower-case country codes.
 
 197     public function setCountryList($aCountries)
 
 199         $this->sqlCountryList = '('.join(',', array_map('addQuotes', $aCountries)).')';
 
 200         $this->aCountryList = $aCountries;
 
 204      * Extract a reference point from a query string.
 
 206      * @param string $sQuery Query to scan.
 
 208      * @return string The remaining query string.
 
 210     public function setNearPointFromQuery($sQuery)
 
 212         $aResult = parseLatLon($sQuery);
 
 214         if ($aResult !== false
 
 215             && $aResult[1] <= 90.1
 
 216             && $aResult[1] >= -90.1
 
 217             && $aResult[2] <= 180.1
 
 218             && $aResult[2] >= -180.1
 
 220             $this->setNearPoint($aResult[1], $aResult[2]);
 
 221             $sQuery = trim(str_replace($aResult[0], ' ', $sQuery));
 
 228      * Get an SQL snippet for computing the distance from the reference point.
 
 230      * @param string $sObj SQL variable name to compute the distance from.
 
 232      * @return string An SQL string.
 
 234     public function distanceSQL($sObj)
 
 236         return 'ST_Distance('.$this->sqlNear.", $sObj)";
 
 240      * Get an SQL snippet for checking if something is within range of the
 
 243      * @param string $sObj SQL variable name to compute if it is within range.
 
 245      * @return string An SQL string.
 
 247     public function withinSQL($sObj)
 
 249         return sprintf('ST_DWithin(%s, %s, %F)', $sObj, $this->sqlNear, $this->fNearRadius);
 
 253      * Get an SQL snippet of the importance factor of the viewbox.
 
 255      * The importance factor is computed by checking if an object is within
 
 256      * the viewbox and/or the extended version of the viewbox.
 
 258      * @param string $sObj SQL variable name of object to weight the importance
 
 260      * @return string SQL snippet of the factor with a leading multiply sign.
 
 262     public function viewboxImportanceSQL($sObj)
 
 266         if ($this->sqlViewboxSmall) {
 
 267             $sSQL = " * CASE WHEN ST_Contains($this->sqlViewboxSmall, $sObj) THEN 1 ELSE 0.5 END";
 
 269         if ($this->sqlViewboxLarge) {
 
 270             $sSQL = " * CASE WHEN ST_Contains($this->sqlViewboxLarge, $sObj) THEN 1 ELSE 0.5 END";
 
 277      * SQL snippet checking if a place ID should be excluded.
 
 279      * @param string $sVariable SQL variable name of place ID to check,
 
 280      *                          potentially prefixed with more SQL.
 
 282      * @return string SQL snippet.
 
 284     public function excludeSQL($sVariable)
 
 286         if ($this->sqlExcludeList) {
 
 287             return $sVariable.$this->sqlExcludeList;
 
 294      * Check if the given country is covered by the search context.
 
 296      * @param string $sCountryCode  Country code of the country to check.
 
 298      * @return True, if no country code restrictions are set or the
 
 299      *         country is included in the country list.
 
 301     public function isCountryApplicable($sCountryCode)
 
 303         return $this->aCountryList === null || in_array($sCountryCode, $this->aCountryList);
 
 306     public function debugInfo()
 
 309                 'Near radius' => $this->fNearRadius,
 
 310                 'Near point (SQL)' => $this->sqlNear,
 
 311                 'Bounded viewbox' => $this->bViewboxBounded,
 
 312                 'Viewbox (SQL, small)' => $this->sqlViewboxSmall,
 
 313                 'Viewbox (SQL, large)' => $this->sqlViewboxLarge,
 
 314                 'Viewbox (SQL, centre)' => $this->sqlViewboxCentre,
 
 315                 'Countries (SQL)' => $this->sqlCountryList,
 
 316                 'Excluded IDs (SQL)' => $this->sqlExcludeList