]> git.openstreetmap.org Git - nominatim.git/blob - lib-php/tokenizer/legacy_tokenizer.php
6760057d93b2eead9ae74d7a2719fd5e77658f57
[nominatim.git] / lib-php / tokenizer / legacy_tokenizer.php
1 <?php
2
3 namespace Nominatim;
4
5 class Tokenizer
6 {
7     private $oDB;
8
9     private $oNormalizer = null;
10
11     public function __construct(&$oDB)
12     {
13         $this->oDB =& $oDB;
14         $this->oNormalizer = \Transliterator::createFromRules(CONST_Term_Normalization_Rules);
15     }
16
17     public function checkStatus()
18     {
19         $sStandardWord = $this->oDB->getOne("SELECT make_standard_name('a')");
20         if ($sStandardWord === false) {
21             throw new \Exception('Module failed', 701);
22         }
23
24         if ($sStandardWord != 'a') {
25             throw new \Exception('Module call failed', 702);
26         }
27
28         $sSQL = "SELECT word_id FROM word WHERE word_token IN (' a')";
29         $iWordID = $this->oDB->getOne($sSQL);
30         if ($iWordID === false) {
31             throw new \Exception('Query failed', 703);
32         }
33         if (!$iWordID) {
34             throw new \Exception('No value', 704);
35         }
36     }
37
38
39     public function normalizeString($sTerm)
40     {
41         if ($this->oNormalizer === null) {
42             return $sTerm;
43         }
44
45         return $this->oNormalizer->transliterate($sTerm);
46     }
47
48
49     public function tokensForSpecialTerm($sTerm)
50     {
51         $aResults = array();
52
53         $sSQL = 'SELECT word_id, class, type FROM word ';
54         $sSQL .= '   WHERE word_token = \' \' || make_standard_name(:term)';
55         $sSQL .= '   AND class is not null AND class not in (\'place\')';
56
57         Debug::printVar('Term', $sTerm);
58         Debug::printSQL($sSQL);
59         $aSearchWords = $this->oDB->getAll($sSQL, array(':term' => $sTerm));
60
61         Debug::printVar('Results', $aSearchWords);
62
63         foreach ($aSearchWords as $aSearchTerm) {
64             $aResults[] = new \Nominatim\Token\SpecialTerm(
65                 $aSearchTerm['word_id'],
66                 $aSearchTerm['class'],
67                 $aSearchTerm['type'],
68                 \Nominatim\Operator::TYPE
69             );
70         }
71
72         Debug::printVar('Special term tokens', $aResults);
73
74         return $aResults;
75     }
76
77
78     public function extractTokensFromPhrases(&$aPhrases)
79     {
80         // First get the normalized version of all phrases
81         $sNormQuery = '';
82         $sSQL = 'SELECT ';
83         $aParams = array();
84         foreach ($aPhrases as $iPhrase => $oPhrase) {
85             $sNormQuery .= ','.$this->normalizeString($oPhrase->getPhrase());
86             $sSQL .= 'make_standard_name(:' .$iPhrase.') as p'.$iPhrase.',';
87             $aParams[':'.$iPhrase] = $oPhrase->getPhrase();
88         }
89         $sSQL = substr($sSQL, 0, -1);
90
91         Debug::printSQL($sSQL);
92         Debug::printVar('SQL parameters', $aParams);
93
94         $aNormPhrases = $this->oDB->getRow($sSQL, $aParams);
95
96         Debug::printVar('SQL result', $aNormPhrases);
97
98         // now compute all possible tokens
99         $aWordLists = array();
100         $aTokens = array();
101         foreach ($aNormPhrases as $sPhrase) {
102             if (strlen($sPhrase) > 0) {
103                 $aWords = explode(' ', $sPhrase);
104                 Tokenizer::addTokens($aTokens, $aWords);
105                 $aWordLists[] = $aWords;
106             } else {
107                 $aWordLists[] = array();
108             }
109         }
110
111         Debug::printVar('Tokens', $aTokens);
112         Debug::printVar('WordLists', $aWordLists);
113
114         $oValidTokens = $this->computeValidTokens($aTokens, $sNormQuery);
115
116         foreach ($aPhrases as $iPhrase => $oPhrase) {
117             $oPhrase->computeWordSets($aWordLists[$iPhrase], $oValidTokens);
118         }
119
120         return $oValidTokens;
121     }
122
123
124     private function computeValidTokens($aTokens, $sNormQuery)
125     {
126         $oValidTokens = new TokenList();
127
128         if (!empty($aTokens)) {
129             $this->addTokensFromDB($oValidTokens, $aTokens, $sNormQuery);
130
131             // Try more interpretations for Tokens that could not be matched.
132             foreach ($aTokens as $sToken) {
133                 if ($sToken[0] != ' ' && !$oValidTokens->contains($sToken)) {
134                     if (preg_match('/^([0-9]{5}) [0-9]{4}$/', $sToken, $aData)) {
135                         // US ZIP+4 codes - merge in the 5-digit ZIP code
136                         $oValidTokens->addToken(
137                             $sToken,
138                             new Token\Postcode(null, $aData[1], 'us')
139                         );
140                     } elseif (preg_match('/^[0-9]+$/', $sToken)) {
141                         // Unknown single word token with a number.
142                         // Assume it is a house number.
143                         $oValidTokens->addToken(
144                             $sToken,
145                             new Token\HouseNumber(null, trim($sToken))
146                         );
147                     }
148                 }
149             }
150         }
151
152         return $oValidTokens;
153     }
154
155
156     private function addTokensFromDB(&$oValidTokens, $aTokens, $sNormQuery)
157     {
158         // Check which tokens we have, get the ID numbers
159         $sSQL = 'SELECT word_id, word_token, word, class, type, country_code,';
160         $sSQL .= ' operator, coalesce(search_name_count, 0) as count';
161         $sSQL .= ' FROM word WHERE word_token in (';
162         $sSQL .= join(',', $this->oDB->getDBQuotedList($aTokens)).')';
163
164         Debug::printSQL($sSQL);
165
166         $aDBWords = $this->oDB->getAll($sSQL, null, 'Could not get word tokens.');
167
168         foreach ($aDBWords as $aWord) {
169             $oToken = null;
170             $iId = (int) $aWord['word_id'];
171
172             if ($aWord['class']) {
173                 // Special terms need to appear in their normalized form.
174                 // (postcodes are not normalized in the word table)
175                 $sNormWord = $this->normalizeString($aWord['word']);
176                 if ($aWord['word'] && strpos($sNormQuery, $sNormWord) === false) {
177                     continue;
178                 }
179
180                 if ($aWord['class'] == 'place' && $aWord['type'] == 'house') {
181                     $oToken = new Token\HouseNumber($iId, trim($aWord['word_token']));
182                 } elseif ($aWord['class'] == 'place' && $aWord['type'] == 'postcode') {
183                     if ($aWord['word']
184                         && pg_escape_string($aWord['word']) == $aWord['word']
185                     ) {
186                         $oToken = new Token\Postcode(
187                             $iId,
188                             $aWord['word'],
189                             $aWord['country_code']
190                         );
191                     }
192                 } else {
193                     // near and in operator the same at the moment
194                     $oToken = new Token\SpecialTerm(
195                         $iId,
196                         $aWord['class'],
197                         $aWord['type'],
198                         $aWord['operator'] ? Operator::NEAR : Operator::NONE
199                     );
200                 }
201             } elseif ($aWord['country_code']) {
202                 $oToken = new Token\Country($iId, $aWord['country_code']);
203             } elseif ($aWord['word_token'][0] == ' ') {
204                 $oToken = new Token\Word(
205                     $iId,
206                     (int) $aWord['count'],
207                     substr_count($aWord['word_token'], ' ')
208                 );
209             // For backward compatibility: ignore all partial tokens with more
210             // than one word.
211             } elseif (strpos($aWord['word_token'], ' ') === false) {
212                 $oToken = new Token\Partial(
213                     $iId,
214                     $aWord['word_token'],
215                     (int) $aWord['count']
216                 );
217             }
218
219             if ($oToken) {
220                 // remove any leading spaces
221                 if ($aWord['word_token'][0] == ' ') {
222                     $oValidTokens->addToken(substr($aWord['word_token'], 1), $oToken);
223                 } else {
224                     $oValidTokens->addToken($aWord['word_token'], $oToken);
225                 }
226             }
227         }
228     }
229
230
231     /**
232      * Add the tokens from this phrase to the given list of tokens.
233      *
234      * @param string[] $aTokens List of tokens to append.
235      *
236      * @return void
237      */
238     private static function addTokens(&$aTokens, $aWords)
239     {
240         $iNumWords = count($aWords);
241
242         for ($i = 0; $i < $iNumWords; $i++) {
243             $sPhrase = $aWords[$i];
244             $aTokens[' '.$sPhrase] = ' '.$sPhrase;
245             $aTokens[$sPhrase] = $sPhrase;
246
247             for ($j = $i + 1; $j < $iNumWords; $j++) {
248                 $sPhrase .= ' '.$aWords[$j];
249                 $aTokens[' '.$sPhrase] = ' '.$sPhrase;
250                 $aTokens[$sPhrase] = $sPhrase;
251             }
252         }
253     }
254 }