]> git.openstreetmap.org Git - nominatim.git/blob - lib-php/SearchContext.php
Merge pull request #2373 from lonvia/tweak-search-cost
[nominatim.git] / lib-php / SearchContext.php
1 <?php
2
3 namespace Nominatim;
4
5 require_once(CONST_LibDir.'/lib.php');
6
7
8 /**
9  * Collection of search constraints that are independent of the
10  * actual interpretation of the search query.
11  *
12  * The search context is shared between all SearchDescriptions. This
13  * object mainly serves as context provider for the database queries.
14  * Therefore most data is directly cached as SQL statements.
15  */
16 class SearchContext
17 {
18     /// Search radius around a given Near reference point.
19     private $fNearRadius = false;
20     /// True if search must be restricted to viewbox only.
21     public $bViewboxBounded = false;
22
23     /// Reference point for search (as SQL).
24     public $sqlNear = '';
25     /// Viewbox selected for search (as SQL).
26     public $sqlViewboxSmall = '';
27     /// Viewbox with a larger buffer around (as SQL).
28     public $sqlViewboxLarge = '';
29     /// Reference along a route (as SQL).
30     public $sqlViewboxCentre = '';
31     /// List of countries to restrict search to (as SQL).
32     public $sqlCountryList = '';
33     /// List of place IDs to exclude (as SQL).
34     private $sqlExcludeList = '';
35     /// Subset of word ids of full words in the query.
36     private $aFullNameWords = array();
37
38     public function setFullNameWords($aWordList)
39     {
40         $this->aFullNameWords = $aWordList;
41     }
42
43     public function getFullNameTerms()
44     {
45         return $this->aFullNameWords;
46     }
47
48     /**
49      * Check if a reference point is defined.
50      *
51      * @return bool True if a reference point is defined.
52      */
53     public function hasNearPoint()
54     {
55         return $this->fNearRadius !== false;
56     }
57
58     /**
59      * Get radius around reference point.
60      *
61      * @return float Search radius around reference point.
62      */
63     public function nearRadius()
64     {
65         return $this->fNearRadius;
66     }
67
68     /**
69      * Set search reference point in WGS84.
70      *
71      * If set, then only places around this point will be taken into account.
72      *
73      * @param float $fLat    Latitude of point.
74      * @param float $fLon    Longitude of point.
75      * @param float $fRadius Search radius around point.
76      *
77      * @return void
78      */
79     public function setNearPoint($fLat, $fLon, $fRadius = 0.1)
80     {
81         $this->fNearRadius = $fRadius;
82         $this->sqlNear = 'ST_SetSRID(ST_Point('.$fLon.','.$fLat.'),4326)';
83     }
84
85     /**
86      * Check if the search is geographically restricted.
87      *
88      * Searches are restricted if a reference point is given or if
89      * a bounded viewbox is set.
90      *
91      * @return bool True, if the search is geographically bounded.
92      */
93     public function isBoundedSearch()
94     {
95         return $this->hasNearPoint() || ($this->sqlViewboxSmall && $this->bViewboxBounded);
96     }
97
98     /**
99      * Set rectangular viewbox.
100      *
101      * The viewbox may be bounded which means that no search results
102      * must be outside the viewbox.
103      *
104      * @param float[4] $aViewBox Coordinates of the viewbox.
105      * @param bool     $bBounded True if the viewbox is bounded.
106      *
107      * @return void
108      */
109     public function setViewboxFromBox(&$aViewBox, $bBounded)
110     {
111         $this->bViewboxBounded = $bBounded;
112         $this->sqlViewboxCentre = '';
113
114         $this->sqlViewboxSmall = sprintf(
115             'ST_SetSRID(ST_MakeBox2D(ST_Point(%F,%F),ST_Point(%F,%F)),4326)',
116             $aViewBox[0],
117             $aViewBox[1],
118             $aViewBox[2],
119             $aViewBox[3]
120         );
121
122         $fHeight = abs($aViewBox[0] - $aViewBox[2]);
123         $fWidth = abs($aViewBox[1] - $aViewBox[3]);
124
125         $this->sqlViewboxLarge = sprintf(
126             'ST_SetSRID(ST_MakeBox2D(ST_Point(%F,%F),ST_Point(%F,%F)),4326)',
127             max($aViewBox[0], $aViewBox[2]) + $fHeight,
128             max($aViewBox[1], $aViewBox[3]) + $fWidth,
129             min($aViewBox[0], $aViewBox[2]) - $fHeight,
130             min($aViewBox[1], $aViewBox[3]) - $fWidth
131         );
132     }
133
134     /**
135      * Set viewbox along a route.
136      *
137      * The viewbox may be bounded which means that no search results
138      * must be outside the viewbox.
139      *
140      * @param object   $oDB          Nominatim::DB instance to use for computing the box.
141      * @param string[] $aRoutePoints List of x,y coordinates along a route.
142      * @param float    $fRouteWidth  Buffer around the route to use.
143      * @param bool     $bBounded     True if the viewbox bounded.
144      *
145      * @return void
146      */
147     public function setViewboxFromRoute(&$oDB, $aRoutePoints, $fRouteWidth, $bBounded)
148     {
149         $this->bViewboxBounded = $bBounded;
150         $this->sqlViewboxCentre = "ST_SetSRID('LINESTRING(";
151         $sSep = '';
152         foreach ($aRoutePoints as $aPoint) {
153             $fPoint = (float)$aPoint;
154             $this->sqlViewboxCentre .= $sSep.$fPoint;
155             $sSep = ($sSep == ' ') ? ',' : ' ';
156         }
157         $this->sqlViewboxCentre .= ")'::geometry,4326)";
158
159         $sSQL = 'ST_BUFFER('.$this->sqlViewboxCentre.','.($fRouteWidth/69).')';
160         $sGeom = $oDB->getOne('select '.$sSQL, null, 'Could not get small viewbox');
161         $this->sqlViewboxSmall = "'".$sGeom."'::geometry";
162
163         $sSQL = 'ST_BUFFER('.$this->sqlViewboxCentre.','.($fRouteWidth/30).')';
164         $sGeom = $oDB->getOne('select '.$sSQL, null, 'Could not get large viewbox');
165         $this->sqlViewboxLarge = "'".$sGeom."'::geometry";
166     }
167
168     /**
169      * Set list of excluded place IDs.
170      *
171      * @param integer[] $aExcluded List of IDs.
172      *
173      * @return void
174      */
175     public function setExcludeList($aExcluded)
176     {
177         $this->sqlExcludeList = ' not in ('.join(',', $aExcluded).')';
178     }
179
180     /**
181      * Set list of countries to restrict search to.
182      *
183      * @param string[] $aCountries List of two-letter lower-case country codes.
184      *
185      * @return void
186      */
187     public function setCountryList($aCountries)
188     {
189         $this->sqlCountryList = '('.join(',', array_map('addQuotes', $aCountries)).')';
190     }
191
192     /**
193      * Extract a reference point from a query string.
194      *
195      * @param string $sQuery Query to scan.
196      *
197      * @return string The remaining query string.
198      */
199     public function setNearPointFromQuery($sQuery)
200     {
201         $aResult = parseLatLon($sQuery);
202
203         if ($aResult !== false
204             && $aResult[1] <= 90.1
205             && $aResult[1] >= -90.1
206             && $aResult[2] <= 180.1
207             && $aResult[2] >= -180.1
208         ) {
209             $this->setNearPoint($aResult[1], $aResult[2]);
210             $sQuery = trim(str_replace($aResult[0], ' ', $sQuery));
211         }
212
213         return $sQuery;
214     }
215
216     /**
217      * Get an SQL snippet for computing the distance from the reference point.
218      *
219      * @param string $sObj SQL variable name to compute the distance from.
220      *
221      * @return string An SQL string.
222      */
223     public function distanceSQL($sObj)
224     {
225         return 'ST_Distance('.$this->sqlNear.", $sObj)";
226     }
227
228     /**
229      * Get an SQL snippet for checking if something is within range of the
230      * reference point.
231      *
232      * @param string $sObj SQL variable name to compute if it is within range.
233      *
234      * @return string An SQL string.
235      */
236     public function withinSQL($sObj)
237     {
238         return sprintf('ST_DWithin(%s, %s, %F)', $sObj, $this->sqlNear, $this->fNearRadius);
239     }
240
241     /**
242      * Get an SQL snippet of the importance factor of the viewbox.
243      *
244      * The importance factor is computed by checking if an object is within
245      * the viewbox and/or the extended version of the viewbox.
246      *
247      * @param string $sObj SQL variable name of object to weight the importance
248      *
249      * @return string SQL snippet of the factor with a leading multiply sign.
250      */
251     public function viewboxImportanceSQL($sObj)
252     {
253         $sSQL = '';
254
255         if ($this->sqlViewboxSmall) {
256             $sSQL = " * CASE WHEN ST_Contains($this->sqlViewboxSmall, $sObj) THEN 1 ELSE 0.5 END";
257         }
258         if ($this->sqlViewboxLarge) {
259             $sSQL = " * CASE WHEN ST_Contains($this->sqlViewboxLarge, $sObj) THEN 1 ELSE 0.5 END";
260         }
261
262         return $sSQL;
263     }
264
265     /**
266      * SQL snippet checking if a place ID should be excluded.
267      *
268      * @param string $sVariable SQL variable name of place ID to check,
269      *                          potentially prefixed with more SQL.
270      *
271      * @return string SQL snippet.
272      */
273     public function excludeSQL($sVariable)
274     {
275         if ($this->sqlExcludeList) {
276             return $sVariable.$this->sqlExcludeList;
277         }
278
279         return '';
280     }
281
282     public function debugInfo()
283     {
284         return array(
285                 'Near radius' => $this->fNearRadius,
286                 'Near point (SQL)' => $this->sqlNear,
287                 'Bounded viewbox' => $this->bViewboxBounded,
288                 'Viewbox (SQL, small)' => $this->sqlViewboxSmall,
289                 'Viewbox (SQL, large)' => $this->sqlViewboxLarge,
290                 'Viewbox (SQL, centre)' => $this->sqlViewboxCentre,
291                 'Countries (SQL)' => $this->sqlCountryList,
292                 'Excluded IDs (SQL)' => $this->sqlExcludeList
293                );
294     }
295 }