source: subversion/sites/namefinder/php/locality.php @ 17445

Last change on this file since 17445 was 8132, checked in by david, 11 years ago

namefinder version 2 - incremental updates; word indexes

File size: 12.2 KB
Line 
1<?php
2
3class locality {
4
5  function explodeterms($terms) {
6    $terms = str_replace(' near ',',',$terms);
7    $terms = explode(',', $terms);
8    for ($i = 0; $i < count($terms); $i++) { $terms[$i] = trim($terms[$i]); }
9    return $terms;
10  }
11
12  function islatlon($term1, $term2, &$pseudoplace) {
13    static $anonid = 0;
14    if (preg_match('/^-?([0-9]+|[0-9]*\\.[0-9]+)$/', $term1) &&
15        preg_match('/^-?([0-9]+|[0-9]*\\.[0-9]+)$/', $term2)) {
16      $lat = (double)$term1;
17      $lon = (double)$term2;
18      $pseudoplace = new named();
19      $pseudoplace->category = 'place';
20      $pseudoplace->lat = $lat;
21      $pseudoplace->lon = $lon;
22      $pseudoplace->name = '';
23      $anonid -= 10;
24      $pseudoplace->id = $anonid; 
25      $pseudoplace->canon = '#;;#';
26      $pseudoplace->rank = named::placerank('town'); // hmm
27      $pseudoplace->info = 'requested location';
28      return TRUE;
29    }
30    return FALSE;
31  }
32
33  function find($lat,$lon) {
34    /* find is named or named,place or named,place,isin; near is lat,lon or empty */
35    global $db, $config;
36
37    include_once('canon.php');
38    include_once('named.php');
39    include_once('region.php');
40
41    $db->log("find lat/lon");
42    $pseudoplace = new named();
43    $pseudoplace->category = 'place';
44    $pseudoplace->lat = $lat;
45    $pseudoplace->lon = $lon;
46    $pseudoplace->name = '';
47    $anonid -= 10;
48    $pseudoplace->id = $anonid; 
49    $pseudoplace->canon = '#;;#';
50    $pseudoplace->rank = named::placerank('town'); // hmm
51    $pseudoplace->info = 'requested location';
52
53    $hierarchy = array('city'=>2, 'town'=>6, 'suburb'=>6, 'village'=>6, 'hamlet'=>6);
54    $numberofresults = array();
55    $maxhitsplacetype = '';
56    $maxhitsplacecount = 0;
57
58    foreach($hierarchy as $placetype=>$numberof) {
59      $places = $pseudoplace->findseveralplace(named::placerank($placetype), $numberof);
60      $numberofresults[$placetype] = count($places);
61      if ($numberofresults[$placetype] > $maxhitsplacecount) {
62        $maxhitsplacecount = $numberofresults[$placetype];
63        $maxhitsplacetype = $placetype;
64      }
65      foreach($places as $place) {
66        $place->localdistancefrom($pseudoplace);
67      }
68    }
69
70    $pseudoplace->assigndescription($nterms > 1);
71    $nameds[] = clone $pseudoplace; 
72
73
74    $toofars = array(0=>10.0, 
75                     named::placerank('hamlet')=>8.0,
76                     named::placerank('village')=>20.0,
77                     named::placerank('suburb')=>20.0,
78                     named::placerank('airport')=>20.0,
79                     named::placerank('town')=>25.0,
80                     named::placerank('city')=>45.0);
81
82    $places = array();
83    $nameds = array();
84    $nterms = count($terms);
85
86    if ($nterms > 2 && search::islatlon($terms[1], $terms[2], $pseudoplace)) {
87      array_splice($terms, 1, 2);
88      $nterms -=2;
89      $places[] = clone $pseudoplace;
90    } else if ($nterms > 1) {
91
92      if (search::islatlon($terms[0], $terms[1], $pseudoplace)) {
93      } else {
94
95        $places = array_merge($places, named::lookupplaces($terms[1]));
96        if (count($places) == 0) { return "I can't find {$terms[1]}"; }
97
98        // $db->log ("found places " . print_r($places, 1));
99
100        /* cull the possible places according to given qualifying isin */
101        $placeisin = $nterms > 2 ? array_slice($terms, 2) : array();
102        if (! empty($placeisin)) {
103          foreach($placeisin as $isin) {
104            $isin = canon::canonical($isin);
105            for ($i = 0; $i < count($places); $i++) {
106              $sourceisin = canon::canonical($places[$i]->is_in);
107              if (strpos($sourceisin, $isin) === FALSE) {
108                array_splice($places, $i, 1);
109                break 2;
110              }
111            }
112          }
113
114          // $db->log ("places after cull " . print_r($places, 1));
115        }
116
117        if (count($places) == 0) { 
118          $isin = '';
119          $prefix = '';
120          for ($i = 2; $i < count($terms); $i++) { 
121            $isin = "{$prefix}{$terms[$i]}";
122            $prefix = ', ';
123          }
124          $unfoundplace = "{$terms[1]} not found";
125          if (! empty($isin)) { $unfoundplace .= " in {$isin}"; }
126        }
127      }
128    }
129
130    /* special case for some plural objects: search on churches near ... => church near */
131    if ($terms[0] == 'churches') { $terms[0] = 'church'; }
132    else if ($terms[0] == 'cities') { $terms[0] = 'city'; }
133
134    $names = canon::canonical_with_synonym($terms[0]);
135
136    if (count($places) > 0) {
137      /* select * from names where (region=n0 [or region=n1 or ...])
138         order by ((lat - latplace)^2 + (lon - lonplace)^2 asc  */
139      foreach ($places as $place) {
140        $place->assigncontext(); // nearest more important place(s)
141
142        /* find occurences of the name ordered by distance from the place,
143           for each of the places we found */
144        $q = $db->query();
145        $ands = array();
146        $ands[] = canon::likecanon($names);
147        $region = new region($place->lat, $place->lon);
148        $regionnumbers = $region->considerregions();
149        $regionors = array();
150        foreach ($regionnumbers as $regionnumber) {
151          $regionors[] = y_op::eq('region', $regionnumber);
152        }
153        $ands[] = count($regionors) == 1 ? $regionors[0] : y_op::oor($regionors);
154
155        $q->where(y_op::aand($ands));
156        $q->ascending(canon::distancerestriction($place->lat, $place->lon));
157        $q->limit($config['limit']);
158
159        $named = new named();
160       
161        $toofar = empty($toofars[$place->rank]) ? $toofars[0]: $toofars[$place->rank];
162
163        while ($q->select($named) > 0) { 
164
165          $named->place = clone $place;
166          $named->place->localdistancefrom($named);
167
168          if ($named->place->distance > $toofar) { break; } // everywhere else is further too
169
170          unset($named->placenearer);
171          $named->findnearestplace(/* other than... */ $place);
172          if (! empty($named->placenearer) && 
173              $named->place->distance < $named->placenearer->distance) 
174          {
175            unset($named->placenearer);
176          }
177          $named->assigndescription($nterms > 1);
178          $nameds[] = clone $named; 
179        }
180
181        // $db->log ("found names near those places " . print_r($nameds, 1));       
182      }
183    }
184
185    if (count($nameds) == 0) {
186      /* no name found near given place (if any): try general search for name: "but I did
187         find one near..." */
188      $limit = $config['limit'];
189      $exact = TRUE; 
190      for ($i = 0; $i < 2 && $limit > 0; $i++) {
191        $q = $db->query();
192        $q->limit($limit);
193        $condition = canon::likecanon($names, $exact);
194        if (! $exact) {
195          $condition = y_op::aand($condition, y_op::not(canon::likecanon($names, TRUE)));
196        }
197        // $condition = y_op::aand($condition, y_op::le('rank',named::placerank('city')));
198        $q->where($condition);
199        /* prioritise places, and those in order of importance, cities first */
200        $q->descending('rank');
201        $named= new named();
202        while ($q->select($named) > 0) { 
203          $namedclone = clone $named;
204          if ($namedclone->rank > 0) {
205            $namedclone->assigncontext();
206            $namedclone->findnearestplace($namedclone, $namedclone->isolatedplaceneighbourranks());
207          } else {
208            $namedclone->findnearestplace();
209          }
210          if (isset($unfoundplace)) { $namedclone->place = $unfoundplace; }
211          $namedclone->assigndescription($nterms > 1);
212          $nameds[] = $namedclone;
213        }
214        $limit -= count($nameds);
215        $exact = FALSE;
216      }
217
218      // $db->log ("found names near other places " . print_r($nameds, 1));       
219    }
220
221    return $nameds;
222  }
223
224  // --------------------------------------------------
225  function xmlise($find) {
226    include_once('postcodelookup.php');
227    global $db;
228    include_once('options.php');
229    $oxml = '<' . '?' . 'xml version="1.0" encoding="UTF-8"' . '?'. '>' . "\n";
230    $oxml .= "<searchresults find='" . htmlspecialchars($find, ENT_QUOTES, 'UTF-8') . "'";
231    $indexdate = options::getoption('indexdate');
232    if ($indexdate == '') {
233      $oxml .= " error='updating index, temporarily unavailable'>\n</searchresults>\n";
234      return $oxml;
235    }
236    $oxml .= " sourcedate='".
237      htmlspecialchars($indexdate, ENT_QUOTES, 'UTF-8')."'";
238    $oxml .= " date='".date('Y-m-d H:i:s')."'";
239
240    /* remove comments in square brackets from input */
241    $find = trim(preg_replace('/\\[.*\\]/', '', $find));
242
243    /* does it look like a postcode? */
244    $postcodelookup = postcodelookup::postcodelookupfactory($find);
245    $originalpostcode = $find;
246    $postcodified = $postcodelookup->get_query($find);
247
248    $finds = explode(':', $find);
249    if (count($finds) > 2) { 
250      $oxml .= " error='too many colons'>\n</searchresults>\n";
251      return $oxml;
252    }
253    for($i = 0; $i < count($finds); $i++) { $finds[$i] = trim($finds[$i]); }
254    /* no closing > yet: adding find data to header and possibly errors */
255
256    $oxml .= " distancesearch='" . (count($finds) > 1 ? 'yes' : 'no') . "'";
257
258    $near = NULL; // isset($_GET['near']) ? $_GET['near'] : NULL;
259    $multinameds = array();
260
261    for($i = 0; $i < count($finds); $i++) {
262      $thisfind = search::explodeterms($finds[$i]);
263
264      $nameds = search::find($thisfind);
265
266      $ks = count($finds) > 1 ? $i+1 : '';
267      if (count($thisfind) >= 1) {
268        $oxml .= " findname{$ks}='" . htmlspecialchars($thisfind[0], ENT_QUOTES, 'UTF-8')."'";
269        if (count($thisfind) >= 2) {
270          $oxml .= " findplace{$ks}='" . htmlspecialchars($thisfind[1], ENT_QUOTES, 'UTF-8')."'";
271          if (count($thisfind) == 3) {
272            $oxml .= " findisin{$ks}='" . htmlspecialchars($thisfind[2], ENT_QUOTES, 'UTF-8')."'";
273          }
274        }
275      }
276
277      if (is_string($nameds)) {
278        if (count($thisfind) == 2) {
279          $thisfind = array_merge(array($thisfind[0]), $thisfind);
280          $nameds = search::find($thisfind);
281        }
282      } 
283
284      if (is_string($nameds)) {
285        $oxml .= " error='place not found'>\n</searchresults>\n";
286        return $oxml;
287      }
288
289      if (count($nameds) == 0) {
290        if ($postcodified) {
291          $oxml .= " error='name not found for postcode'>\n</searchresults>\n";
292        } else {
293          $oxml .= " error='name not found'>\n</searchresults>\n";
294        }
295        return $oxml;
296      }
297
298      $foundnearplace = ! empty($nameds[0]->place);
299      $oxml .= " foundnearplace{$ks}='" . ($foundnearplace ? 'yes' : 'no') . "'";
300         
301      if ($postcodified) { $oxml .= " postcode='{$originalpostcode}'"; }
302
303      $db->log("result: ".print_r($nameds,1));
304      $multinameds[] = $nameds;
305    }
306
307    $oxml .= ">\n";
308    $xml = '';
309
310    if (count($multinameds) == 1) {
311      foreach($nameds as $named) { $xml .= $named->xmlise(); }
312    } else {
313      include_once('greatcircle.php');
314      $gcs = array();
315      for ($i0 = 0; $i0 < min(count($multinameds[0]), 3); $i0++) {
316        $output0 = FALSE;
317        if ($multinameds[0][$i0]->category != 'place') { break; }
318        for ($i1 = 0; $i1 < min(count($multinameds[1]), 3); $i1++) {
319          if ($multinameds[1][$i1]->category != 'place') { break; }
320          if (! $output0) { 
321            $xml .= $multinameds[0][$i0]->xmlise();
322            $output0 = TRUE;
323          }
324          $xml .= $multinameds[1][$i1]->xmlise();
325          $gcs[] = new greatcircle($multinameds[0][$i0], $multinameds[1][$i1]);
326        }
327      }
328      for ($i0 = 0; $i0 < min(count($multinameds[0]), 3); $i0++) {
329        $output0 = FALSE;
330        if ($multinameds[0][$i0]->category == 'place') { continue; }
331        for ($i1 = 0; $i1 < min(count($multinameds[1]), 3); $i1++) {
332          if ($multinameds[1][$i1]->category == 'place') { continue; }
333          if (! $output0) { 
334            $xml .= $multinameds[0][$i0]->xmlise();
335            $output0 = TRUE;
336          }
337          $xml .= $multinameds[1][$i1]->xmlise();
338          $gcs[] = new greatcircle($multinameds[0][$i0], $multinameds[1][$i1]);
339        }
340      }
341      foreach ($gcs as $gc) { $xml .= $gc->xmlise(); }
342    }
343    return $oxml . $xml . "</searchresults>\n";
344  }
345}
346
347
348?>
Note: See TracBrowser for help on using the repository browser.