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