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