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