]> git.openstreetmap.org Git - nominatim.git/blob - lib-php/SearchContext.php
Merge pull request #3367 from lonvia/address-word-counts
[nominatim.git] / lib-php / SearchContext.php
1 <?php
2 /**
3  * SPDX-License-Identifier: GPL-2.0-only
4  *
5  * This file is part of Nominatim. (https://nominatim.org)
6  *
7  * Copyright (C) 2022 by the Nominatim developer community.
8  * For a full list of authors see the git log.
9  */
10
11 namespace Nominatim;
12
13 require_once(CONST_LibDir.'/lib.php');
14
15
16 /**
17  * Collection of search constraints that are independent of the
18  * actual interpretation of the search query.
19  *
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.
23  */
24 class SearchContext
25 {
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;
30
31     /// Reference point for search (as SQL).
32     public $sqlNear = '';
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();
47
48     public function setFullNameWords($aWordList)
49     {
50         $this->aFullNameWords = $aWordList;
51     }
52
53     public function getFullNameTerms()
54     {
55         return $this->aFullNameWords;
56     }
57
58     /**
59      * Check if a reference point is defined.
60      *
61      * @return bool True if a reference point is defined.
62      */
63     public function hasNearPoint()
64     {
65         return $this->fNearRadius !== false;
66     }
67
68     /**
69      * Get radius around reference point.
70      *
71      * @return float Search radius around reference point.
72      */
73     public function nearRadius()
74     {
75         return $this->fNearRadius;
76     }
77
78     /**
79      * Set search reference point in WGS84.
80      *
81      * If set, then only places around this point will be taken into account.
82      *
83      * @param float $fLat    Latitude of point.
84      * @param float $fLon    Longitude of point.
85      * @param float $fRadius Search radius around point.
86      *
87      * @return void
88      */
89     public function setNearPoint($fLat, $fLon, $fRadius = 0.1)
90     {
91         $this->fNearRadius = $fRadius;
92         $this->sqlNear = 'ST_SetSRID(ST_Point('.$fLon.','.$fLat.'),4326)';
93     }
94
95     /**
96      * Check if the search is geographically restricted.
97      *
98      * Searches are restricted if a reference point is given or if
99      * a bounded viewbox is set.
100      *
101      * @return bool True, if the search is geographically bounded.
102      */
103     public function isBoundedSearch()
104     {
105         return $this->hasNearPoint() || ($this->sqlViewboxSmall && $this->bViewboxBounded);
106     }
107
108     /**
109      * Set rectangular viewbox.
110      *
111      * The viewbox may be bounded which means that no search results
112      * must be outside the viewbox.
113      *
114      * @param float[4] $aViewBox Coordinates of the viewbox.
115      * @param bool     $bBounded True if the viewbox is bounded.
116      *
117      * @return void
118      */
119     public function setViewboxFromBox(&$aViewBox, $bBounded)
120     {
121         $this->bViewboxBounded = $bBounded;
122         $this->sqlViewboxCentre = '';
123
124         $this->sqlViewboxSmall = sprintf(
125             'ST_SetSRID(ST_MakeBox2D(ST_Point(%F,%F),ST_Point(%F,%F)),4326)',
126             $aViewBox[0],
127             $aViewBox[1],
128             $aViewBox[2],
129             $aViewBox[3]
130         );
131
132         $fHeight = abs($aViewBox[0] - $aViewBox[2]);
133         $fWidth = abs($aViewBox[1] - $aViewBox[3]);
134
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
141         );
142     }
143
144     /**
145      * Set viewbox along a route.
146      *
147      * The viewbox may be bounded which means that no search results
148      * must be outside the viewbox.
149      *
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.
154      *
155      * @return void
156      */
157     public function setViewboxFromRoute(&$oDB, $aRoutePoints, $fRouteWidth, $bBounded)
158     {
159         $this->bViewboxBounded = $bBounded;
160         $this->sqlViewboxCentre = "ST_SetSRID('LINESTRING(";
161         $sSep = '';
162         foreach ($aRoutePoints as $aPoint) {
163             $fPoint = (float)$aPoint;
164             $this->sqlViewboxCentre .= $sSep.$fPoint;
165             $sSep = ($sSep == ' ') ? ',' : ' ';
166         }
167         $this->sqlViewboxCentre .= ")'::geometry,4326)";
168
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";
172
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";
176     }
177
178     /**
179      * Set list of excluded place IDs.
180      *
181      * @param integer[] $aExcluded List of IDs.
182      *
183      * @return void
184      */
185     public function setExcludeList($aExcluded)
186     {
187         $this->sqlExcludeList = ' not in ('.join(',', $aExcluded).')';
188     }
189
190     /**
191      * Set list of countries to restrict search to.
192      *
193      * @param string[] $aCountries List of two-letter lower-case country codes.
194      *
195      * @return void
196      */
197     public function setCountryList($aCountries)
198     {
199         $this->sqlCountryList = '('.join(',', array_map('addQuotes', $aCountries)).')';
200         $this->aCountryList = $aCountries;
201     }
202
203     /**
204      * Extract a reference point from a query string.
205      *
206      * @param string $sQuery Query to scan.
207      *
208      * @return string The remaining query string.
209      */
210     public function setNearPointFromQuery($sQuery)
211     {
212         $aResult = parseLatLon($sQuery);
213
214         if ($aResult !== false
215             && $aResult[1] <= 90.1
216             && $aResult[1] >= -90.1
217             && $aResult[2] <= 180.1
218             && $aResult[2] >= -180.1
219         ) {
220             $this->setNearPoint($aResult[1], $aResult[2]);
221             $sQuery = trim(str_replace($aResult[0], ' ', $sQuery));
222         }
223
224         return $sQuery;
225     }
226
227     /**
228      * Get an SQL snippet for computing the distance from the reference point.
229      *
230      * @param string $sObj SQL variable name to compute the distance from.
231      *
232      * @return string An SQL string.
233      */
234     public function distanceSQL($sObj)
235     {
236         return 'ST_Distance('.$this->sqlNear.", $sObj)";
237     }
238
239     /**
240      * Get an SQL snippet for checking if something is within range of the
241      * reference point.
242      *
243      * @param string $sObj SQL variable name to compute if it is within range.
244      *
245      * @return string An SQL string.
246      */
247     public function withinSQL($sObj)
248     {
249         return sprintf('ST_DWithin(%s, %s, %F)', $sObj, $this->sqlNear, $this->fNearRadius);
250     }
251
252     /**
253      * Get an SQL snippet of the importance factor of the viewbox.
254      *
255      * The importance factor is computed by checking if an object is within
256      * the viewbox and/or the extended version of the viewbox.
257      *
258      * @param string $sObj SQL variable name of object to weight the importance
259      *
260      * @return string SQL snippet of the factor with a leading multiply sign.
261      */
262     public function viewboxImportanceSQL($sObj)
263     {
264         $sSQL = '';
265
266         if ($this->sqlViewboxSmall) {
267             $sSQL = " * CASE WHEN ST_Contains($this->sqlViewboxSmall, $sObj) THEN 1 ELSE 0.5 END";
268         }
269         if ($this->sqlViewboxLarge) {
270             $sSQL = " * CASE WHEN ST_Contains($this->sqlViewboxLarge, $sObj) THEN 1 ELSE 0.5 END";
271         }
272
273         return $sSQL;
274     }
275
276     /**
277      * SQL snippet checking if a place ID should be excluded.
278      *
279      * @param string $sVariable SQL variable name of place ID to check,
280      *                          potentially prefixed with more SQL.
281      *
282      * @return string SQL snippet.
283      */
284     public function excludeSQL($sVariable)
285     {
286         if ($this->sqlExcludeList) {
287             return $sVariable.$this->sqlExcludeList;
288         }
289
290         return '';
291     }
292
293     /**
294      * Check if the given country is covered by the search context.
295      *
296      * @param string $sCountryCode  Country code of the country to check.
297      *
298      * @return True, if no country code restrictions are set or the
299      *         country is included in the country list.
300      */
301     public function isCountryApplicable($sCountryCode)
302     {
303         return $this->aCountryList === null || in_array($sCountryCode, $this->aCountryList);
304     }
305
306     public function debugInfo()
307     {
308         return array(
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
317                );
318     }
319 }