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');
8 ini_set('memory_limit', '800M');
10 use Nominatim\Setup\SetupFunctions as SetupFunctions;
12 // (long-opt, short-opt, min-occurs, max-occurs, num-arguments, num-arguments, type, help)
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'),
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'),
27 array('calculate-postcodes', '', 0, 1, 0, 0, 'bool', 'Update postcode centroid table'),
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'),
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'),
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)'),
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')
47 getCmdOpt($_SERVER['argv'], $aCMDOptions, $aResult, true, true);
51 if (!isset($aResult['index-instances'])) $aResult['index-instances'] = 1;
52 if (!isset($aResult['index-rank'])) $aResult['index-rank'] = 0;
54 date_default_timezone_set('Etc/UTC');
56 $oDB = new Nominatim\DB();
58 $fPostgresVersion = $oDB->getPostgresVersion();
60 $aDSNInfo = Nominatim\DB::parseDSN(getSetting('DATABASE_DSN'));
61 if (!isset($aDSNInfo['port']) || !$aDSNInfo['port']) $aDSNInfo['port'] = 5432;
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";
70 $oOsm2pgsqlCmd = (new \Nominatim\Shell(getOsm2pgsqlBinary()))
71 ->addParams('--hstore')
72 ->addParams('--latlong')
73 ->addParams('--append')
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']);
84 if (isset($aDSNInfo['hostspec']) && $aDSNInfo['hostspec']) {
85 $oOsm2pgsqlCmd->addParams('--host', $aDSNInfo['hostspec']);
87 if (isset($aDSNInfo['username']) && $aDSNInfo['username']) {
88 $oOsm2pgsqlCmd->addParams('--user', $aDSNInfo['username']);
90 if (isset($aDSNInfo['password']) && $aDSNInfo['password']) {
91 $oOsm2pgsqlCmd->addEnvPair('PGPASSWORD', $aDSNInfo['password']);
93 if (getSetting('FLATNODE_FILE')) {
94 $oOsm2pgsqlCmd->addParams('--flat-nodes', getSetting('FLATNODE_FILE'));
96 if ($fPostgresVersion >= 11.0) {
97 $oOsm2pgsqlCmd->addEnvPair(
99 '-c jit=off -c max_parallel_workers_per_gather=0'
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');
111 if ($aResult['verbose']) {
112 $oIndexCmd->addParams('--verbose');
114 if (isset($aDSNInfo['hostspec']) && $aDSNInfo['hostspec']) {
115 $oIndexCmd->addParams('--host', $aDSNInfo['hostspec']);
117 if (isset($aDSNInfo['username']) && $aDSNInfo['username']) {
118 $oIndexCmd->addParams('--username', $aDSNInfo['username']);
120 if (isset($aDSNInfo['password']) && $aDSNInfo['password']) {
121 $oIndexCmd->addEnvPair('PGPASSWORD', $aDSNInfo['password']);
124 $sPyosmiumBin = getSetting('PYOSMIUM_BINARY');
125 $sBaseURL = getSetting('REPLICATION_URL');
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.');
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');
145 $oCMD = new \Nominatim\Shell($sPyosmiumBin, '--help');
146 exec($oCMD->escapedCmd(), $aOutput, $iRet);
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');
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']
161 $cSetup->createFunctions();
164 $sDatabaseDate = getDatabaseDate($oDB);
165 if (!$sDatabaseDate) {
166 fail('Cannot determine date of database.');
168 $sWindBack = strftime('%Y-%m-%dT%H:%M:%SZ', strtotime($sDatabaseDate) - (3*60*60));
170 // get the appropriate state id
172 $oCMD = (new \Nominatim\Shell($sPyosmiumBin))
173 ->addParams('--start-date', $sWindBack)
174 ->addParams('--server', $sBaseURL);
176 exec($oCMD->escapedCmd(), $aOutput, $iRet);
177 if ($iRet != 0 || $aOutput[0] == 'None') {
178 fail('Error running pyosmium tools');
181 $oDB->exec('TRUNCATE import_status');
182 $sSQL = "INSERT INTO import_status (lastimportdate, sequence_id, indexed) VALUES('";
183 $sSQL .= $sDatabaseDate."',".$aOutput[0].', true)';
187 } catch (\Nominatim\DatabaseError $e) {
188 fail('Could not enter sequence into database.');
191 echo "Done. Database updates will start at sequence $aOutput[0] ($sWindBack)\n";
194 if ($aResult['check-for-updates']) {
195 $aLastState = $oDB->getRow('SELECT sequence_id FROM import_status');
197 if (!$aLastState['sequence_id']) {
198 fail('Updates not set up. Please run ./utils/update.php --init-updates.');
201 $oCmd = (new \Nominatim\Shell(CONST_BinDir.'/check_server_for_updates.py'))
202 ->addParams($sBaseURL)
203 ->addParams($aLastState['sequence_id']);
204 $iRet = $oCmd->run();
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'];
213 if (!file_exists($sNextFile)) {
214 fail("Cannot open $sNextFile\n");
218 $oCMD = (clone $oOsm2pgsqlCmd)->addParams($sNextFile);
219 echo $oCMD->escapedCmd()."\n";
220 $iRet = $oCMD->run();
223 fail("Error from osm2pgsql, $iRet\n");
226 // Don't update the import status - we don't know what this file contains
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);
235 $sTemporaryFile = CONST_InstallDir.'/osmosischange.osc';
237 $bUseOSMApi = isset($aResult['import-from-main-api']) && $aResult['import-from-main-api'];
239 if (isset($aResult['import-node']) && $aResult['import-node']) {
241 $sContentURL = 'https://www.openstreetmap.org/api/0.6/node/'.$aResult['import-node'];
243 $sContentURL = 'https://overpass-api.de/api/interpreter?data=node('.$aResult['import-node'].');out%20meta;';
247 if (isset($aResult['import-way']) && $aResult['import-way']) {
249 $sContentURL = 'https://www.openstreetmap.org/api/0.6/way/'.$aResult['import-way'].'/full';
251 $sContentURL = 'https://overpass-api.de/api/interpreter?data=(way('.$aResult['import-way'].');%3E;);out%20meta;';
255 if (isset($aResult['import-relation']) && $aResult['import-relation']) {
257 $sContentURL = 'https://www.openstreetmap.org/api/0.6/relation/'.$aResult['import-relation'].'/full';
259 $sContentURL = 'https://overpass-api.de/api/interpreter?data=(rel(id:'.$aResult['import-relation'].');%3E;);out%20meta;';
264 file_put_contents($sTemporaryFile, file_get_contents($sContentURL));
269 // import generated change file
271 $oCMD = (clone $oOsm2pgsqlCmd)->addParams($sTemporaryFile);
272 echo $oCMD->escapedCmd()."\n";
274 $iRet = $oCMD->run();
276 fail("osm2pgsql exited with error level $iRet\n");
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);
286 if ($aResult['index']) {
287 $oCmd = (clone $oIndexCmd)
288 ->addParams('--minrank', $aResult['index-rank'], '-b');
291 $oCmd = (clone $oIndexCmd)
292 ->addParams('--minrank', $aResult['index-rank']);
295 $oDB->exec('update import_status set indexed = true');
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');
305 if ($aResult['recompute-importance']) {
306 echo "Updating importance values for database.\n";
307 $oDB = new Nominatim\DB();
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;';
322 if ($aResult['import-osmosis'] || $aResult['import-osmosis-all']) {
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");
329 $sImportFile = CONST_InstallDir.'/osmosischange.osc';
331 $oCMDDownload = (new \Nominatim\Shell($sPyosmiumBin))
332 ->addParams('--server', $sBaseURL)
333 ->addParams('--outfile', $sImportFile)
334 ->addParams('--size', getSetting('REPLICATION_MAX_DIFF'));
336 $oCMDImport = (clone $oOsm2pgsqlCmd)->addParams($sImportFile);
339 $fStartTime = time();
340 $aLastState = $oDB->getRow('SELECT *, EXTRACT (EPOCH FROM lastimportdate) as unix_ts FROM import_status');
342 if (!$aLastState['sequence_id']) {
343 echo "Updates not set up. Please run ./utils/update.php --init-updates.\n";
347 echo 'Currently at sequence '.$aLastState['sequence_id'].' ('.$aLastState['lastimportdate'].') - '.$aLastState['indexed']." indexed\n";
349 $sBatchEnd = $aLastState['lastimportdate'];
350 $iEndSequence = $aLastState['sequence_id'];
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.";
361 // Download the next batch of changes.
363 $fCMDStartTime = time();
364 $iNextSeq = (int) $aLastState['sequence_id'];
367 $oCMD = (clone $oCMDDownload)->addParams('--start-id', $iNextSeq);
368 echo $oCMD->escapedCmd()."\n";
369 if (file_exists($sImportFile)) {
370 unlink($sImportFile);
372 exec($oCMD->escapedCmd(), $aOutput, $iResult);
375 $sSleep = getSetting('REPLICATION_RECHECK_INTERVAL');
376 echo 'No new updates. Sleeping for '.$sSleep." sec.\n";
378 } elseif ($iResult != 0) {
379 echo 'ERROR: updates failed.';
382 $iEndSequence = (int)$aOutput[0];
386 // get the newest object from the diff file
389 $oCMD = new \Nominatim\Shell(CONST_BinDir.'/osm_file_date.py', $sImportFile);
390 exec($oCMD->escapedCmd(), $sBatchEnd, $iRet);
392 echo "Diff file is empty. skipping import.\n";
393 if (!$aResult['import-osmosis-all']) {
400 fail('Error getting date from diff file.');
402 $sBatchEnd = $sBatchEnd[0];
405 $fCMDStartTime = time();
408 echo $oCMDImport->escapedCmd()."\n";
410 $iErrorLevel = $oCMDImport->run();
412 echo "Error executing osm2pgsql: $iErrorLevel\n";
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')";
427 $sSQL = "UPDATE import_status SET lastimportdate = '$sBatchEnd', indexed=false, sequence_id = $iEndSequence";
430 echo date('Y-m-d H:i:s')." Completed download step for $sBatchEnd in ".round((time()-$fCMDStartTime)/60, 2)." minutes\n";
434 if (!$aResult['no-index']) {
435 $fCMDStartTime = time();
437 $oThisIndexCmd = clone($oIndexCmd);
438 $oThisIndexCmd->addParams('-b');
439 echo $oThisIndexCmd->escapedCmd()."\n";
440 $iErrorLevel = $oThisIndexCmd->run();
442 echo "Error: $iErrorLevel\n";
446 $oThisIndexCmd = clone($oIndexCmd);
447 echo $oThisIndexCmd->escapedCmd()."\n";
448 $iErrorLevel = $oThisIndexCmd->run();
450 echo "Error: $iErrorLevel\n";
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')";
461 echo date('Y-m-d H:i:s')." Completed index step for $sBatchEnd in ".round((time()-$fCMDStartTime)/60, 2)." minutes\n";
463 $sSQL = 'update import_status set indexed = true';
466 if ($aResult['import-osmosis-all']) {
467 echo "Error: --no-index cannot be used with continuous imports (--import-osmosis-all).\n";
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);