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