]> git.openstreetmap.org Git - nominatim.git/blob - lib/admin/update.php
Merge remote-tracking branch 'upstream/master'
[nominatim.git] / lib / admin / update.php
1 <?php
2 @define('CONST_LibDir', dirname(dirname(__FILE__)));
3
4 require_once(CONST_LibDir.'/init-cmd.php');
5 require_once(CONST_LibDir.'/setup_functions.php');
6 require_once(CONST_LibDir.'/setup/SetupClass.php');
7 require_once(CONST_LibDir.'/setup/AddressLevelParser.php');
8
9 ini_set('memory_limit', '800M');
10
11 use Nominatim\Setup\SetupFunctions as SetupFunctions;
12
13 // (long-opt, short-opt, min-occurs, max-occurs, num-arguments, num-arguments, type, help)
14 $aCMDOptions
15 = array(
16    'Import / update / index osm data',
17    array('help', 'h', 0, 1, 0, 0, false, 'Show Help'),
18    array('quiet', 'q', 0, 1, 0, 0, 'bool', 'Quiet output'),
19    array('verbose', 'v', 0, 1, 0, 0, 'bool', 'Verbose output'),
20
21    array('init-updates', '', 0, 1, 0, 0, 'bool', 'Set up database for updating'),
22    array('check-for-updates', '', 0, 1, 0, 0, 'bool', 'Check if new updates are available'),
23    array('no-update-functions', '', 0, 1, 0, 0, 'bool', 'Do not update trigger functions to support differential updates (assuming the diff update logic is already present)'),
24    array('import-osmosis', '', 0, 1, 0, 0, 'bool', 'Import updates once'),
25    array('import-osmosis-all', '', 0, 1, 0, 0, 'bool', 'Import updates forever'),
26    array('no-index', '', 0, 1, 0, 0, 'bool', 'Do not index the new data'),
27
28    array('calculate-postcodes', '', 0, 1, 0, 0, 'bool', 'Update postcode centroid table'),
29
30    array('import-file', '', 0, 1, 1, 1, 'realpath', 'Re-import data from an OSM file'),
31    array('import-diff', '', 0, 1, 1, 1, 'realpath', 'Import a diff (osc) file from local file system'),
32    array('osm2pgsql-cache', '', 0, 1, 1, 1, 'int', 'Cache size used by osm2pgsql'),
33
34    array('import-node', '', 0, 1, 1, 1, 'int', 'Re-import node'),
35    array('import-way', '', 0, 1, 1, 1, 'int', 'Re-import way'),
36    array('import-relation', '', 0, 1, 1, 1, 'int', 'Re-import relation'),
37    array('import-from-main-api', '', 0, 1, 0, 0, 'bool', 'Use OSM API instead of Overpass to download objects'),
38
39    array('index', '', 0, 1, 0, 0, 'bool', 'Index'),
40    array('index-rank', '', 0, 1, 1, 1, 'int', 'Rank to start indexing from'),
41    array('index-instances', '', 0, 1, 1, 1, 'int', 'Number of indexing instances (threads)'),
42
43    array('recompute-word-counts', '', 0, 1, 0, 0, 'bool', 'Compute frequency of full-word search terms'),
44    array('update-address-levels', '', 0, 1, 0, 0, 'bool', 'Reimport address level configuration (EXPERT)'),
45    array('recompute-importance', '', 0, 1, 0, 0, 'bool', 'Recompute place importances'),
46
47    array('project-dir', '', 0, 1, 1, 1, 'realpath', 'Base directory of the Nominatim installation (default: .)'),
48   );
49
50 getCmdOpt($_SERVER['argv'], $aCMDOptions, $aResult, true, true);
51
52 loadSettings($aCMDResult['project-dir'] ?? getcwd());
53 setupHTTPProxy();
54
55 if (!isset($aResult['index-instances'])) $aResult['index-instances'] = 1;
56
57 if (!isset($aResult['index-rank'])) $aResult['index-rank'] = 0;
58
59 date_default_timezone_set('Etc/UTC');
60
61 $oDB = new Nominatim\DB();
62 $oDB->connect();
63 $fPostgresVersion = $oDB->getPostgresVersion();
64
65 $aDSNInfo = Nominatim\DB::parseDSN(getSetting('DATABASE_DSN'));
66 if (!isset($aDSNInfo['port']) || !$aDSNInfo['port']) $aDSNInfo['port'] = 5432;
67
68 // cache memory to be used by osm2pgsql, should not be more than the available memory
69 $iCacheMemory = (isset($aResult['osm2pgsql-cache'])?$aResult['osm2pgsql-cache']:2000);
70 if ($iCacheMemory + 500 > getTotalMemoryMB()) {
71     $iCacheMemory = getCacheMemoryMB();
72     echo "WARNING: resetting cache memory to $iCacheMemory\n";
73 }
74
75 $oOsm2pgsqlCmd = (new \Nominatim\Shell(getOsm2pgsqlBinary()))
76                  ->addParams('--hstore')
77                  ->addParams('--latlong')
78                  ->addParams('--append')
79                  ->addParams('--slim')
80                  ->addParams('--with-forward-dependencies', 'false')
81                  ->addParams('--log-progress', 'true')
82                  ->addParams('--number-processes', 1)
83                  ->addParams('--cache', $iCacheMemory)
84                  ->addParams('--output', 'gazetteer')
85                  ->addParams('--style', getImportStyle())
86                  ->addParams('--database', $aDSNInfo['database'])
87                  ->addParams('--port', $aDSNInfo['port']);
88
89 if (isset($aDSNInfo['hostspec']) && $aDSNInfo['hostspec']) {
90     $oOsm2pgsqlCmd->addParams('--host', $aDSNInfo['hostspec']);
91 }
92 if (isset($aDSNInfo['username']) && $aDSNInfo['username']) {
93     $oOsm2pgsqlCmd->addParams('--user', $aDSNInfo['username']);
94 }
95 if (isset($aDSNInfo['password']) && $aDSNInfo['password']) {
96     $oOsm2pgsqlCmd->addEnvPair('PGPASSWORD', $aDSNInfo['password']);
97 }
98 if (getSetting('FLATNODE_FILE')) {
99     $oOsm2pgsqlCmd->addParams('--flat-nodes', getSetting('FLATNODE_FILE'));
100 }
101 if ($fPostgresVersion >= 11.0) {
102     $oOsm2pgsqlCmd->addEnvPair(
103         'PGOPTIONS',
104         '-c jit=off -c max_parallel_workers_per_gather=0'
105     );
106 }
107
108
109 $oIndexCmd = (new \Nominatim\Shell(CONST_DataDir.'/nominatim/nominatim.py'))
110              ->addParams('--database', $aDSNInfo['database'])
111              ->addParams('--port', $aDSNInfo['port'])
112              ->addParams('--threads', $aResult['index-instances']);
113 if (!$aResult['quiet']) {
114     $oIndexCmd->addParams('--verbose');
115 }
116 if ($aResult['verbose']) {
117     $oIndexCmd->addParams('--verbose');
118 }
119 if (isset($aDSNInfo['hostspec']) && $aDSNInfo['hostspec']) {
120     $oIndexCmd->addParams('--host', $aDSNInfo['hostspec']);
121 }
122 if (isset($aDSNInfo['username']) && $aDSNInfo['username']) {
123     $oIndexCmd->addParams('--username', $aDSNInfo['username']);
124 }
125 if (isset($aDSNInfo['password']) && $aDSNInfo['password']) {
126     $oIndexCmd->addEnvPair('PGPASSWORD', $aDSNInfo['password']);
127 }
128
129 $sPyosmiumBin = getSetting('PYOSMIUM_BINARY');
130 $sBaseURL = getSetting('REPLICATION_URL');
131
132
133 if ($aResult['init-updates']) {
134     // sanity check that the replication URL is correct
135     $sBaseState = file_get_contents($sBaseURL.'/state.txt');
136     if ($sBaseState === false) {
137         echo "\nCannot find state.txt file at the configured replication URL.\n";
138         echo "Does the URL point to a directory containing OSM update data?\n\n";
139         fail('replication URL not reachable.');
140     }
141     // sanity check for pyosmium-get-changes
142     if (!$sPyosmiumBin) {
143         echo "\nNOMINATIM_PYOSMIUM_BINARY not configured.\n";
144         echo "You need to install pyosmium and set up the path to pyosmium-get-changes\n";
145         echo "in your local .env file.\n\n";
146         fail('NOMINATIM_PYOSMIUM_BINARY not configured');
147     }
148
149     $aOutput = 0;
150     $oCMD = new \Nominatim\Shell($sPyosmiumBin, '--help');
151     exec($oCMD->escapedCmd(), $aOutput, $iRet);
152
153     if ($iRet != 0) {
154         echo "Cannot execute pyosmium-get-changes.\n";
155         echo "Make sure you have pyosmium installed correctly\n";
156         echo "and have set up NOMINATIM_PYOSMIUM_BINARY to point to pyosmium-get-changes.\n";
157         fail('pyosmium-get-changes not found or not usable');
158     }
159
160     if (!$aResult['no-update-functions']) {
161         // instantiate setupClass to use the function therein
162         $cSetup = new SetupFunctions(array(
163                                       'enable-diff-updates' => true,
164                                       'verbose' => $aResult['verbose']
165                                      ));
166         $cSetup->createFunctions();
167     }
168
169     $sDatabaseDate = getDatabaseDate($oDB);
170     if (!$sDatabaseDate) {
171         fail('Cannot determine date of database.');
172     }
173     $sWindBack = strftime('%Y-%m-%dT%H:%M:%SZ', strtotime($sDatabaseDate) - (3*60*60));
174
175     // get the appropriate state id
176     $aOutput = 0;
177     $oCMD = (new \Nominatim\Shell($sPyosmiumBin))
178             ->addParams('--start-date', $sWindBack)
179             ->addParams('--server', $sBaseURL);
180
181     exec($oCMD->escapedCmd(), $aOutput, $iRet);
182     if ($iRet != 0 || $aOutput[0] == 'None') {
183         fail('Error running pyosmium tools');
184     }
185
186     $oDB->exec('TRUNCATE import_status');
187     $sSQL = "INSERT INTO import_status (lastimportdate, sequence_id, indexed) VALUES('";
188     $sSQL .= $sDatabaseDate."',".$aOutput[0].', true)';
189
190     try {
191         $oDB->exec($sSQL);
192     } catch (\Nominatim\DatabaseError $e) {
193         fail('Could not enter sequence into database.');
194     }
195
196     echo "Done. Database updates will start at sequence $aOutput[0] ($sWindBack)\n";
197 }
198
199 if ($aResult['check-for-updates']) {
200     $aLastState = $oDB->getRow('SELECT sequence_id FROM import_status');
201
202     if (!$aLastState['sequence_id']) {
203         fail('Updates not set up. Please run ./utils/update.php --init-updates.');
204     }
205
206     $oCmd = (new \Nominatim\Shell(CONST_BinDir.'/check_server_for_updates.py'))
207             ->addParams($sBaseURL)
208             ->addParams($aLastState['sequence_id']);
209     $iRet = $oCmd->run();
210
211     exit($iRet);
212 }
213
214 if (isset($aResult['import-diff']) || isset($aResult['import-file'])) {
215     // import diffs and files directly (e.g. from osmosis --rri)
216     $sNextFile = isset($aResult['import-diff']) ? $aResult['import-diff'] : $aResult['import-file'];
217
218     if (!file_exists($sNextFile)) {
219         fail("Cannot open $sNextFile\n");
220     }
221
222     // Import the file
223     $oCMD = (clone $oOsm2pgsqlCmd)->addParams($sNextFile);
224     echo $oCMD->escapedCmd()."\n";
225     $iRet = $oCMD->run();
226
227     if ($iRet) {
228         fail("Error from osm2pgsql, $iRet\n");
229     }
230
231     // Don't update the import status - we don't know what this file contains
232 }
233
234 if ($aResult['calculate-postcodes']) {
235     info('Update postcodes centroids');
236     $sTemplate = file_get_contents(CONST_DataDir.'/sql/update-postcodes.sql');
237     runSQLScript($sTemplate, true, true);
238 }
239
240 $sTemporaryFile = CONST_InstallDir.'/osmosischange.osc';
241 $bHaveDiff = false;
242 $bUseOSMApi = isset($aResult['import-from-main-api']) && $aResult['import-from-main-api'];
243 $sContentURL = '';
244 if (isset($aResult['import-node']) && $aResult['import-node']) {
245     if ($bUseOSMApi) {
246         $sContentURL = 'https://www.openstreetmap.org/api/0.6/node/'.$aResult['import-node'];
247     } else {
248         $sContentURL = 'https://overpass-api.de/api/interpreter?data=node('.$aResult['import-node'].');out%20meta;';
249     }
250 }
251
252 if (isset($aResult['import-way']) && $aResult['import-way']) {
253     if ($bUseOSMApi) {
254         $sContentURL = 'https://www.openstreetmap.org/api/0.6/way/'.$aResult['import-way'].'/full';
255     } else {
256         $sContentURL = 'https://overpass-api.de/api/interpreter?data=(way('.$aResult['import-way'].');%3E;);out%20meta;';
257     }
258 }
259
260 if (isset($aResult['import-relation']) && $aResult['import-relation']) {
261     if ($bUseOSMApi) {
262         $sContentURL = 'https://www.openstreetmap.org/api/0.6/relation/'.$aResult['import-relation'].'/full';
263     } else {
264         $sContentURL = 'https://overpass-api.de/api/interpreter?data=(rel(id:'.$aResult['import-relation'].');%3E;);out%20meta;';
265     }
266 }
267
268 if ($sContentURL) {
269     file_put_contents($sTemporaryFile, file_get_contents($sContentURL));
270     $bHaveDiff = true;
271 }
272
273 if ($bHaveDiff) {
274     // import generated change file
275
276     $oCMD = (clone $oOsm2pgsqlCmd)->addParams($sTemporaryFile);
277     echo $oCMD->escapedCmd()."\n";
278
279     $iRet = $oCMD->run();
280     if ($iRet) {
281         fail("osm2pgsql exited with error level $iRet\n");
282     }
283 }
284
285 if ($aResult['recompute-word-counts']) {
286     info('Recompute frequency of full-word search terms');
287     $sTemplate = file_get_contents(CONST_DataDir.'/sql/words_from_search_name.sql');
288     runSQLScript($sTemplate, true, true);
289 }
290
291 if ($aResult['index']) {
292     $oCmd = (clone $oIndexCmd)
293             ->addParams('--minrank', $aResult['index-rank'], '-b');
294     $oCmd->run();
295
296     $oCmd = (clone $oIndexCmd)
297             ->addParams('--minrank', $aResult['index-rank']);
298     $oCmd->run();
299
300     $oDB->exec('update import_status set indexed = true');
301 }
302
303 if ($aResult['update-address-levels']) {
304     $sAddressLevelConfig = getSettingConfig('ADDRESS_LEVEL_CONFIG', 'address-levels.json');
305     echo 'Updating address levels from '.$sAddressLevelConfig.".\n";
306     $oAlParser = new \Nominatim\Setup\AddressLevelParser($sAddressLevelConfig);
307     $oAlParser->createTable($oDB, 'address_levels');
308 }
309
310 if ($aResult['recompute-importance']) {
311     echo "Updating importance values for database.\n";
312     $oDB = new Nominatim\DB();
313     $oDB->connect();
314
315     $sSQL = 'ALTER TABLE placex DISABLE TRIGGER ALL;';
316     $sSQL .= 'UPDATE placex SET (wikipedia, importance) =';
317     $sSQL .= '   (SELECT wikipedia, importance';
318     $sSQL .= '    FROM compute_importance(extratags, country_code, osm_type, osm_id));';
319     $sSQL .= 'UPDATE placex s SET wikipedia = d.wikipedia, importance = d.importance';
320     $sSQL .= ' FROM placex d';
321     $sSQL .= ' WHERE s.place_id = d.linked_place_id and d.wikipedia is not null';
322     $sSQL .= '       and (s.wikipedia is null or s.importance < d.importance);';
323     $sSQL .= 'ALTER TABLE placex ENABLE TRIGGER ALL;';
324     $oDB->exec($sSQL);
325 }
326
327 if ($aResult['import-osmosis'] || $aResult['import-osmosis-all']) {
328     //
329     if (strpos($sBaseURL, 'download.geofabrik.de') !== false && getSetting('REPLICATION_UPDATE_INTERVAL') < 86400) {
330         fail('Error: Update interval too low for download.geofabrik.de. ' .
331              "Please check install documentation (https://nominatim.org/release-docs/latest/admin/Import-and-Update#setting-up-the-update-process)\n");
332     }
333
334     $sImportFile = CONST_InstallDir.'/osmosischange.osc';
335
336     $oCMDDownload = (new \Nominatim\Shell($sPyosmiumBin))
337                     ->addParams('--server', $sBaseURL)
338                     ->addParams('--outfile', $sImportFile)
339                     ->addParams('--size', getSetting('REPLICATION_MAX_DIFF'));
340
341     $oCMDImport = (clone $oOsm2pgsqlCmd)->addParams($sImportFile);
342
343     while (true) {
344         $fStartTime = time();
345         $aLastState = $oDB->getRow('SELECT *, EXTRACT (EPOCH FROM lastimportdate) as unix_ts FROM import_status');
346
347         if (!$aLastState['sequence_id']) {
348             echo "Updates not set up. Please run ./utils/update.php --init-updates.\n";
349             exit(1);
350         }
351
352         echo 'Currently at sequence '.$aLastState['sequence_id'].' ('.$aLastState['lastimportdate'].') - '.$aLastState['indexed']." indexed\n";
353
354         $sBatchEnd = $aLastState['lastimportdate'];
355         $iEndSequence = $aLastState['sequence_id'];
356
357         if ($aLastState['indexed']) {
358             // Sleep if the update interval has not yet been reached.
359             $fNextUpdate = $aLastState['unix_ts'] + getSetting('REPLICATION_UPDATE_INTERVAL');
360             if ($fNextUpdate > $fStartTime) {
361                 $iSleepTime = $fNextUpdate - $fStartTime;
362                 echo "Waiting for next update for $iSleepTime sec.";
363                 sleep($iSleepTime);
364             }
365
366             // Download the next batch of changes.
367             do {
368                 $fCMDStartTime = time();
369                 $iNextSeq = (int) $aLastState['sequence_id'];
370                 unset($aOutput);
371
372                 $oCMD = (clone $oCMDDownload)->addParams('--start-id', $iNextSeq);
373                 echo $oCMD->escapedCmd()."\n";
374                 if (file_exists($sImportFile)) {
375                     unlink($sImportFile);
376                 }
377                 exec($oCMD->escapedCmd(), $aOutput, $iResult);
378
379                 if ($iResult == 3) {
380                     $sSleep = getSetting('REPLICATION_RECHECK_INTERVAL');
381                     echo 'No new updates. Sleeping for '.$sSleep." sec.\n";
382                     sleep($sSleep);
383                 } elseif ($iResult != 0) {
384                     echo 'ERROR: updates failed.';
385                     exit($iResult);
386                 } else {
387                     $iEndSequence = (int)$aOutput[0];
388                 }
389             } while ($iResult);
390
391             // get the newest object from the diff file
392             $sBatchEnd = 0;
393             $iRet = 0;
394             $oCMD = new \Nominatim\Shell(CONST_BinDir.'/osm_file_date.py', $sImportFile);
395             exec($oCMD->escapedCmd(), $sBatchEnd, $iRet);
396             if ($iRet == 5) {
397                 echo "Diff file is empty. skipping import.\n";
398                 if (!$aResult['import-osmosis-all']) {
399                     exit(0);
400                 } else {
401                     continue;
402                 }
403             }
404             if ($iRet != 0) {
405                 fail('Error getting date from diff file.');
406             }
407             $sBatchEnd = $sBatchEnd[0];
408
409             // Import the file
410             $fCMDStartTime = time();
411
412
413             echo $oCMDImport->escapedCmd()."\n";
414             unset($sJunk);
415             $iErrorLevel = $oCMDImport->run();
416             if ($iErrorLevel) {
417                 echo "Error executing osm2pgsql: $iErrorLevel\n";
418                 exit($iErrorLevel);
419             }
420
421             // write the update logs
422             $iFileSize = filesize($sImportFile);
423             $sSQL = 'INSERT INTO import_osmosis_log';
424             $sSQL .= '(batchend, batchseq, batchsize, starttime, endtime, event)';
425             $sSQL .= " values ('$sBatchEnd',$iEndSequence,$iFileSize,'";
426             $sSQL .= date('Y-m-d H:i:s', $fCMDStartTime)."','";
427             $sSQL .= date('Y-m-d H:i:s')."','import')";
428             var_Dump($sSQL);
429             $oDB->exec($sSQL);
430
431             // update the status
432             $sSQL = "UPDATE import_status SET lastimportdate = '$sBatchEnd', indexed=false, sequence_id = $iEndSequence";
433             var_Dump($sSQL);
434             $oDB->exec($sSQL);
435             echo date('Y-m-d H:i:s')." Completed download step for $sBatchEnd in ".round((time()-$fCMDStartTime)/60, 2)." minutes\n";
436         }
437
438         // Index file
439         if (!$aResult['no-index']) {
440             $fCMDStartTime = time();
441
442             $oThisIndexCmd = clone($oIndexCmd);
443             $oThisIndexCmd->addParams('-b');
444             echo $oThisIndexCmd->escapedCmd()."\n";
445             $iErrorLevel = $oThisIndexCmd->run();
446             if ($iErrorLevel) {
447                 echo "Error: $iErrorLevel\n";
448                 exit($iErrorLevel);
449             }
450
451             $oThisIndexCmd = clone($oIndexCmd);
452             echo $oThisIndexCmd->escapedCmd()."\n";
453             $iErrorLevel = $oThisIndexCmd->run();
454             if ($iErrorLevel) {
455                 echo "Error: $iErrorLevel\n";
456                 exit($iErrorLevel);
457             }
458
459             $sSQL = 'INSERT INTO import_osmosis_log';
460             $sSQL .= '(batchend, batchseq, batchsize, starttime, endtime, event)';
461             $sSQL .= " values ('$sBatchEnd',$iEndSequence,NULL,'";
462             $sSQL .= date('Y-m-d H:i:s', $fCMDStartTime)."','";
463             $sSQL .= date('Y-m-d H:i:s')."','index')";
464             var_Dump($sSQL);
465             $oDB->exec($sSQL);
466             echo date('Y-m-d H:i:s')." Completed index step for $sBatchEnd in ".round((time()-$fCMDStartTime)/60, 2)." minutes\n";
467
468             $sSQL = 'update import_status set indexed = true';
469             $oDB->exec($sSQL);
470         } else {
471             if ($aResult['import-osmosis-all']) {
472                 echo "Error: --no-index cannot be used with continuous imports (--import-osmosis-all).\n";
473                 exit(1);
474             }
475         }
476
477         $fDuration = time() - $fStartTime;
478         echo date('Y-m-d H:i:s')." Completed all for $sBatchEnd in ".round($fDuration/60, 2)." minutes\n";
479         if (!$aResult['import-osmosis-all']) exit(0);
480     }
481 }