]> git.openstreetmap.org Git - nominatim.git/blob - utils/update.php
Merge remote-tracking branch 'upstream/master'
[nominatim.git] / utils / update.php
1 #!/usr/bin/php -Cq
2 <?php
3
4 require_once(dirname(dirname(__FILE__)).'/settings/settings.php');
5 require_once(CONST_BasePath.'/lib/init-cmd.php');
6 ini_set('memory_limit', '800M');
7
8 $aCMDOptions
9 = array(
10    'Import / update / index osm data',
11    array('help', 'h', 0, 1, 0, 0, false, 'Show Help'),
12    array('quiet', 'q', 0, 1, 0, 0, 'bool', 'Quiet output'),
13    array('verbose', 'v', 0, 1, 0, 0, 'bool', 'Verbose output'),
14
15    array('init-updates', '', 0, 1, 0, 0, 'bool', 'Set up database for updating'),
16    array('check-for-updates', '', 0, 1, 0, 0, 'bool', 'Check if new updates are available'),
17    array('import-osmosis', '', 0, 1, 0, 0, 'bool', 'Import updates once'),
18    array('import-osmosis-all', '', 0, 1, 0, 0, 'bool', 'Import updates forever'),
19    array('no-index', '', 0, 1, 0, 0, 'bool', 'Do not index the new data'),
20
21    array('calculate-postcodes', '', 0, 1, 0, 0, 'bool', 'Update postcode centroid table'),
22
23    array('import-file', '', 0, 1, 1, 1, 'realpath', 'Re-import data from an OSM file'),
24    array('import-diff', '', 0, 1, 1, 1, 'realpath', 'Import a diff (osc) file from local file system'),
25    array('osm2pgsql-cache', '', 0, 1, 1, 1, 'int', 'Cache size used by osm2pgsql'),
26
27    array('import-node', '', 0, 1, 1, 1, 'int', 'Re-import node'),
28    array('import-way', '', 0, 1, 1, 1, 'int', 'Re-import way'),
29    array('import-relation', '', 0, 1, 1, 1, 'int', 'Re-import relation'),
30    array('import-from-main-api', '', 0, 1, 0, 0, 'bool', 'Use OSM API instead of Overpass to download objects'),
31
32    array('index', '', 0, 1, 0, 0, 'bool', 'Index'),
33    array('index-rank', '', 0, 1, 1, 1, 'int', 'Rank to start indexing from'),
34    array('index-instances', '', 0, 1, 1, 1, 'int', 'Number of indexing instances (threads)'),
35
36    array('deduplicate', '', 0, 1, 0, 0, 'bool', 'Deduplicate tokens'),
37    array('recompute-word-counts', '', 0, 1, 0, 0, 'bool', 'Compute frequency of full-word search terms'),
38    array('no-npi', '', 0, 1, 0, 0, 'bool', '(obsolete)'),
39   );
40 getCmdOpt($_SERVER['argv'], $aCMDOptions, $aResult, true, true);
41
42 if (!isset($aResult['index-instances'])) $aResult['index-instances'] = 1;
43
44 if (!isset($aResult['index-rank'])) $aResult['index-rank'] = 0;
45
46 date_default_timezone_set('Etc/UTC');
47
48 $oDB =& getDB();
49
50 $aDSNInfo = DB::parseDSN(CONST_Database_DSN);
51 if (!isset($aDSNInfo['port']) || !$aDSNInfo['port']) $aDSNInfo['port'] = 5432;
52
53 // cache memory to be used by osm2pgsql, should not be more than the available memory
54 $iCacheMemory = (isset($aResult['osm2pgsql-cache'])?$aResult['osm2pgsql-cache']:2000);
55 if ($iCacheMemory + 500 > getTotalMemoryMB()) {
56     $iCacheMemory = getCacheMemoryMB();
57     echo "WARNING: resetting cache memory to $iCacheMemory\n";
58 }
59 $sOsm2pgsqlCmd = CONST_Osm2pgsql_Binary.' -klas --number-processes 1 -C '.$iCacheMemory.' -O gazetteer -d '.$aDSNInfo['database'].' -P '.$aDSNInfo['port'];
60 if (!is_null(CONST_Osm2pgsql_Flatnode_File) && CONST_Osm2pgsql_Flatnode_File) {
61     $sOsm2pgsqlCmd .= ' --flat-nodes '.CONST_Osm2pgsql_Flatnode_File;
62 }
63
64 if ($aResult['init-updates']) {
65     // sanity check that the replication URL is correct
66     $sBaseState = file_get_contents(CONST_Replication_Url.'/state.txt');
67     if ($sBaseState === false) {
68         echo "\nCannot find state.txt file at the configured replication URL.\n";
69         echo "Does the URL point to a directory containing OSM update data?\n\n";
70         fail('replication URL not reachable.');
71     }
72     $sSetup = CONST_InstallPath.'/utils/setup.php';
73     $iRet = -1;
74     passthru($sSetup.' --create-functions --enable-diff-updates', $iRet);
75     if ($iRet != 0) {
76         fail('Error running setup script');
77     }
78
79     $sDatabaseDate = getDatabaseDate($oDB);
80     if ($sDatabaseDate === false) {
81         fail('Cannot determine date of database.');
82     }
83     $sWindBack = strftime('%Y-%m-%dT%H:%M:%SZ', strtotime($sDatabaseDate) - (3*60*60));
84
85     // get the appropriate state id
86     $aOutput = 0;
87     $sCmd = CONST_Pyosmium_Binary.' -D '.$sWindBack.' --server '.CONST_Replication_Url;
88     exec($sCmd, $aOutput, $iRet);
89     if ($iRet != 0 || $aOutput[0] == 'None') {
90         fail('Error running pyosmium tools');
91     }
92
93     pg_query($oDB->connection, 'TRUNCATE import_status');
94     $sSQL = "INSERT INTO import_status (lastimportdate, sequence_id, indexed) VALUES('";
95     $sSQL .= $sDatabaseDate."',".$aOutput[0].', true)';
96     if (!pg_query($oDB->connection, $sSQL)) {
97         fail('Could not enter sequence into database.');
98     }
99
100     echo "Done. Database updates will start at sequence $aOutput[0] ($sWindBack)\n";
101 }
102
103 if ($aResult['check-for-updates']) {
104     $aLastState = chksql($oDB->getRow('SELECT sequence_id FROM import_status'));
105
106     if (!$aLastState['sequence_id']) {
107         fail('Updates not set up. Please run ./utils/update.php --init-updates.');
108     }
109
110     system(CONST_BasePath.'/utils/check_server_for_updates.py '.CONST_Replication_Url.' '.$aLastState['sequence_id'], $iRet);
111     exit($iRet);
112 }
113
114 if (isset($aResult['import-diff']) || isset($aResult['import-file'])) {
115     // import diffs and files directly (e.g. from osmosis --rri)
116     $sNextFile = isset($aResult['import-diff']) ? $aResult['import-diff'] : $aResult['import-file'];
117
118     if (!file_exists($sNextFile)) {
119         fail("Cannot open $sNextFile\n");
120     }
121
122     // Import the file
123     $sCMD = $sOsm2pgsqlCmd.' '.$sNextFile;
124     echo $sCMD."\n";
125     exec($sCMD, $sJunk, $iErrorLevel);
126
127     if ($iErrorLevel) {
128         fail("Error from osm2pgsql, $iErrorLevel\n");
129     }
130
131     // Don't update the import status - we don't know what this file contains
132 }
133
134 if ($aResult['calculate-postcodes']) {
135     info('Update postcodes centroids');
136     $sTemplate = file_get_contents(CONST_BasePath.'/sql/update-postcodes.sql');
137     runSQLScript($sTemplate, true, true);
138 }
139
140 $sTemporaryFile = CONST_BasePath.'/data/osmosischange.osc';
141 $bHaveDiff = false;
142 $bUseOSMApi = isset($aResult['import-from-main-api']) && $aResult['import-from-main-api'];
143 $sContentURL = '';
144 if (isset($aResult['import-node']) && $aResult['import-node']) {
145     if ($bUseOSMApi) {
146         $sContentURL = 'https://www.openstreetmap.org/api/0.6/node/'.$aResult['import-node'];
147     } else {
148         $sContentURL = 'https://overpass-api.de/api/interpreter?data=node('.$aResult['import-node'].');out%20meta;';
149     }
150 }
151
152 if (isset($aResult['import-way']) && $aResult['import-way']) {
153     if ($bUseOSMApi) {
154         $sContentURL = 'https://www.openstreetmap.org/api/0.6/way/'.$aResult['import-way'].'/full';
155     } else {
156         $sContentURL = 'https://overpass-api.de/api/interpreter?data=(way('.$aResult['import-way'].');node(w););out%20meta;';
157     }
158 }
159
160 if (isset($aResult['import-relation']) && $aResult['import-relation']) {
161     if ($bUseOSMApi) {
162         $sContentURLsModifyXMLstr = 'https://www.openstreetmap.org/api/0.6/relation/'.$aResult['import-relation'].'/full';
163     } else {
164         $sContentURL = 'https://overpass-api.de/api/interpreter?data=((rel('.$aResult['import-relation'].');way(r);node(w));node(r));out%20meta;';
165     }
166 }
167
168 if ($sContentURL) {
169     file_put_contents($sTemporaryFile, file_get_contents($sContentURL));
170     $bHaveDiff = true;
171 }
172
173 if ($bHaveDiff) {
174     // import generated change file
175     $sCMD = $sOsm2pgsqlCmd.' '.$sTemporaryFile;
176     echo $sCMD."\n";
177     exec($sCMD, $sJunk, $iErrorLevel);
178     if ($iErrorLevel) {
179         fail("osm2pgsql exited with error level $iErrorLevel\n");
180     }
181 }
182
183 if ($aResult['deduplicate']) {
184     $oDB =& getDB();
185
186     if (getPostgresVersion($oDB) < 9.3) {
187         fail('ERROR: deduplicate is only currently supported in postgresql 9.3');
188     }
189
190     $sSQL = 'select partition from country_name order by country_code';
191     $aPartitions = chksql($oDB->getCol($sSQL));
192     $aPartitions[] = 0;
193
194     // we don't care about empty search_name_* partitions, they can't contain mentions of duplicates
195     foreach ($aPartitions as $i => $sPartition) {
196         $sSQL = 'select count(*) from search_name_'.$sPartition;
197         $nEntries = chksql($oDB->getOne($sSQL));
198         if ($nEntries == 0) {
199             unset($aPartitions[$i]);
200         }
201     }
202
203     $sSQL = "select word_token,count(*) from word where substr(word_token, 1, 1) = ' '";
204     $sSQL .= ' and class is null and type is null and country_code is null';
205     $sSQL .= ' group by word_token having count(*) > 1 order by word_token';
206     $aDuplicateTokens = chksql($oDB->getAll($sSQL));
207     foreach ($aDuplicateTokens as $aToken) {
208         if (trim($aToken['word_token']) == '' || trim($aToken['word_token']) == '-') continue;
209         echo 'Deduping '.$aToken['word_token']."\n";
210         $sSQL = 'select word_id,';
211         $sSQL .= ' (select count(*) from search_name where nameaddress_vector @> ARRAY[word_id]) as num';
212         $sSQL .= " from word where word_token = '".$aToken['word_token'];
213         $sSQL .= "' and class is null and type is null and country_code is null order by num desc";
214         $aTokenSet = chksql($oDB->getAll($sSQL));
215
216         $aKeep = array_shift($aTokenSet);
217         $iKeepID = $aKeep['word_id'];
218
219         foreach ($aTokenSet as $aRemove) {
220             $sSQL = 'update search_name set';
221             $sSQL .= ' name_vector = array_replace(name_vector,'.$aRemove['word_id'].','.$iKeepID.'),';
222             $sSQL .= ' nameaddress_vector = array_replace(nameaddress_vector,'.$aRemove['word_id'].','.$iKeepID.')';
223             $sSQL .= ' where name_vector @> ARRAY['.$aRemove['word_id'].']';
224             chksql($oDB->query($sSQL));
225
226             $sSQL = 'update search_name set';
227             $sSQL .= ' nameaddress_vector = array_replace(nameaddress_vector,'.$aRemove['word_id'].','.$iKeepID.')';
228             $sSQL .= ' where nameaddress_vector @> ARRAY['.$aRemove['word_id'].']';
229             chksql($oDB->query($sSQL));
230
231             $sSQL = 'update location_area_country set';
232             $sSQL .= ' keywords = array_replace(keywords,'.$aRemove['word_id'].','.$iKeepID.')';
233             $sSQL .= ' where keywords @> ARRAY['.$aRemove['word_id'].']';
234             chksql($oDB->query($sSQL));
235
236             foreach ($aPartitions as $sPartition) {
237                 $sSQL = 'update search_name_'.$sPartition.' set';
238                 $sSQL .= ' name_vector = array_replace(name_vector,'.$aRemove['word_id'].','.$iKeepID.')';
239                 $sSQL .= ' where name_vector @> ARRAY['.$aRemove['word_id'].']';
240                 chksql($oDB->query($sSQL));
241
242                 $sSQL = 'update location_area_country set';
243                 $sSQL .= ' keywords = array_replace(keywords,'.$aRemove['word_id'].','.$iKeepID.')';
244                 $sSQL .= ' where keywords @> ARRAY['.$aRemove['word_id'].']';
245                 chksql($oDB->query($sSQL));
246             }
247
248             $sSQL = 'delete from word where word_id = '.$aRemove['word_id'];
249             chksql($oDB->query($sSQL));
250         }
251     }
252 }
253
254 if ($aResult['recompute-word-counts']) {
255     info('Recompute frequency of full-word search terms');
256     $sTemplate = file_get_contents(CONST_BasePath.'/sql/words_from_search_name.sql');
257     runSQLScript($sTemplate, true, true);
258 }
259
260 if ($aResult['index']) {
261     passthru(CONST_InstallPath.'/nominatim/nominatim -i -d '.$aDSNInfo['database'].' -P '.$aDSNInfo['port'].' -t '.$aResult['index-instances'].' -r '.$aResult['index-rank']);
262 }
263
264 if ($aResult['import-osmosis'] || $aResult['import-osmosis-all']) {
265     //
266     if (strpos(CONST_Replication_Url, 'download.geofabrik.de') !== false && CONST_Replication_Update_Interval < 86400) {
267         fail('Error: Update interval too low for download.geofabrik.de. ' .
268              "Please check install documentation (http://nominatim.org/release-docs/latest/Import-and-Update#setting-up-the-update-process)\n");
269     }
270
271     $sImportFile = CONST_InstallPath.'/osmosischange.osc';
272     $sCMDDownload = CONST_Pyosmium_Binary.' --server '.CONST_Replication_Url.' -o '.$sImportFile.' -s '.CONST_Replication_Max_Diff_size;
273     $sCMDImport = $sOsm2pgsqlCmd.' '.$sImportFile;
274     $sCMDIndex = CONST_InstallPath.'/nominatim/nominatim -i -d '.$aDSNInfo['database'].' -P '.$aDSNInfo['port'].' -t '.$aResult['index-instances'];
275
276     while (true) {
277         $fStartTime = time();
278         $aLastState = chksql($oDB->getRow('SELECT *, EXTRACT (EPOCH FROM lastimportdate) as unix_ts FROM import_status'));
279
280         if (!$aLastState['sequence_id']) {
281             echo "Updates not set up. Please run ./utils/update.php --init-updates.\n";
282             exit(1);
283         }
284
285         echo 'Currently at sequence '.$aLastState['sequence_id'].' ('.$aLastState['lastimportdate'].') - '.$aLastState['indexed']." indexed\n";
286
287         $sBatchEnd = $aLastState['lastimportdate'];
288         $iEndSequence = $aLastState['sequence_id'];
289
290         if ($aLastState['indexed'] == 't') {
291             // Sleep if the update interval has not yet been reached.
292             $fNextUpdate = $aLastState['unix_ts'] + CONST_Replication_Update_Interval;
293             if ($fNextUpdate > $fStartTime) {
294                 $iSleepTime = $fNextUpdate - $fStartTime;
295                 echo "Waiting for next update for $iSleepTime sec.";
296                 sleep($iSleepTime);
297             }
298
299             // Download the next batch of changes.
300             do {
301                 $fCMDStartTime = time();
302                 $iNextSeq = (int) $aLastState['sequence_id'];
303                 unset($aOutput);
304                 echo "$sCMDDownload -I $iNextSeq\n";
305                 if (file_exists($sImportFile)) {
306                     unlink($sImportFile);
307                 }
308                 exec($sCMDDownload.' -I '.$iNextSeq, $aOutput, $iResult);
309
310                 if ($iResult == 3) {
311                     echo 'No new updates. Sleeping for '.CONST_Replication_Recheck_Interval." sec.\n";
312                     sleep(CONST_Replication_Recheck_Interval);
313                 } elseif ($iResult != 0) {
314                     echo 'ERROR: updates failed.';
315                     exit($iResult);
316                 } else {
317                     $iEndSequence = (int)$aOutput[0];
318                 }
319             } while ($iResult);
320
321             // get the newest object from the diff file
322             $sBatchEnd = 0;
323             $iRet = 0;
324             exec(CONST_BasePath.'/utils/osm_file_date.py '.$sImportFile, $sBatchEnd, $iRet);
325             if ($iRet == 5) {
326                 echo "Diff file is empty. skipping import.\n";
327                 if (!$aResult['import-osmosis-all']) {
328                     exit(0);
329                 } else {
330                     continue;
331                 }
332             }
333             if ($iRet != 0) {
334                 fail('Error getting date from diff file.');
335             }
336             $sBatchEnd = $sBatchEnd[0];
337
338             // Import the file
339             $fCMDStartTime = time();
340             echo $sCMDImport."\n";
341             unset($sJunk);
342             exec($sCMDImport, $sJunk, $iErrorLevel);
343             if ($iErrorLevel) {
344                 echo "Error executing osm2pgsql: $iErrorLevel\n";
345                 exit($iErrorLevel);
346             }
347
348             // write the update logs
349             $iFileSize = filesize($sImportFile);
350             $sSQL = 'INSERT INTO import_osmosis_log';
351             $sSQL .= '(batchend, batchseq, batchsize, starttime, endtime, event)';
352             $sSQL .= " values ('$sBatchEnd',$iEndSequence,$iFileSize,'";
353             $sSQL .= date('Y-m-d H:i:s', $fCMDStartTime)."','";
354             $sSQL .= date('Y-m-d H:i:s')."','import')";
355             var_Dump($sSQL);
356             chksql($oDB->query($sSQL));
357
358             // update the status
359             $sSQL = "UPDATE import_status SET lastimportdate = '$sBatchEnd', indexed=false, sequence_id = $iEndSequence";
360             var_Dump($sSQL);
361             chksql($oDB->query($sSQL));
362             echo date('Y-m-d H:i:s')." Completed download step for $sBatchEnd in ".round((time()-$fCMDStartTime)/60, 2)." minutes\n";
363         }
364
365         // Index file
366         if (!$aResult['no-index']) {
367             $sThisIndexCmd = $sCMDIndex;
368             $fCMDStartTime = time();
369
370             echo "$sThisIndexCmd\n";
371             exec($sThisIndexCmd, $sJunk, $iErrorLevel);
372             if ($iErrorLevel) {
373                 echo "Error: $iErrorLevel\n";
374                 exit($iErrorLevel);
375             }
376
377             $sSQL = 'INSERT INTO import_osmosis_log';
378             $sSQL .= '(batchend, batchseq, batchsize, starttime, endtime, event)';
379             $sSQL .= " values ('$sBatchEnd',$iEndSequence,$iFileSize,'";
380             $sSQL .= date('Y-m-d H:i:s', $fCMDStartTime)."','";
381             $sSQL .= date('Y-m-d H:i:s')."','index')";
382             var_Dump($sSQL);
383             $oDB->query($sSQL);
384             echo date('Y-m-d H:i:s')." Completed index step for $sBatchEnd in ".round((time()-$fCMDStartTime)/60, 2)." minutes\n";
385
386             $sSQL = 'update import_status set indexed = true';
387             $oDB->query($sSQL);
388         }
389
390         $fDuration = time() - $fStartTime;
391         echo date('Y-m-d H:i:s')." Completed all for $sBatchEnd in ".round($fDuration/60, 2)." minutes\n";
392         if (!$aResult['import-osmosis-all']) exit(0);
393     }
394 }