2 @define('CONST_LibDir', dirname(dirname(__FILE__)));
4 require_once(CONST_LibDir.'/init-cmd.php');
5 require_once(CONST_LibDir.'/setup_functions.php');
6 require_once(CONST_LibDir.'/setup/SetupClass.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'),
46 array('project-dir', '', 0, 1, 1, 1, 'realpath', 'Base directory of the Nominatim installation (default: .)'),
49 getCmdOpt($_SERVER['argv'], $aCMDOptions, $aResult, true, true);
51 loadSettings($aCMDResult['project-dir'] ?? getcwd());
54 if (!isset($aResult['index-instances'])) $aResult['index-instances'] = 1;
55 if (!isset($aResult['index-rank'])) $aResult['index-rank'] = 0;
57 date_default_timezone_set('Etc/UTC');
59 $oDB = new Nominatim\DB();
61 $fPostgresVersion = $oDB->getPostgresVersion();
63 $aDSNInfo = Nominatim\DB::parseDSN(getSetting('DATABASE_DSN'));
64 if (!isset($aDSNInfo['port']) || !$aDSNInfo['port']) $aDSNInfo['port'] = 5432;
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";
73 $oOsm2pgsqlCmd = (new \Nominatim\Shell(getOsm2pgsqlBinary()))
74 ->addParams('--hstore')
75 ->addParams('--latlong')
76 ->addParams('--append')
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']);
87 if (isset($aDSNInfo['hostspec']) && $aDSNInfo['hostspec']) {
88 $oOsm2pgsqlCmd->addParams('--host', $aDSNInfo['hostspec']);
90 if (isset($aDSNInfo['username']) && $aDSNInfo['username']) {
91 $oOsm2pgsqlCmd->addParams('--user', $aDSNInfo['username']);
93 if (isset($aDSNInfo['password']) && $aDSNInfo['password']) {
94 $oOsm2pgsqlCmd->addEnvPair('PGPASSWORD', $aDSNInfo['password']);
96 if (getSetting('FLATNODE_FILE')) {
97 $oOsm2pgsqlCmd->addParams('--flat-nodes', getSetting('FLATNODE_FILE'));
99 if ($fPostgresVersion >= 11.0) {
100 $oOsm2pgsqlCmd->addEnvPair(
102 '-c jit=off -c max_parallel_workers_per_gather=0'
106 $oNominatimCmd = new \Nominatim\Shell(getSetting('NOMINATIM_TOOL'));
107 if ($aResult['quiet']) {
108 $oNominatimCmd->addParams('--quiet');
110 if ($aResult['verbose']) {
111 $oNominatimCmd->addParams('--verbose');
114 $sPyosmiumBin = getSetting('PYOSMIUM_BINARY');
115 $sBaseURL = getSetting('REPLICATION_URL');
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.');
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');
135 $oCMD = new \Nominatim\Shell($sPyosmiumBin, '--help');
136 exec($oCMD->escapedCmd(), $aOutput, $iRet);
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');
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']
151 $cSetup->createFunctions();
154 $sDatabaseDate = getDatabaseDate($oDB);
155 if (!$sDatabaseDate) {
156 fail('Cannot determine date of database.');
158 $sWindBack = strftime('%Y-%m-%dT%H:%M:%SZ', strtotime($sDatabaseDate) - (3*60*60));
160 // get the appropriate state id
162 $oCMD = (new \Nominatim\Shell($sPyosmiumBin))
163 ->addParams('--start-date', $sWindBack)
164 ->addParams('--server', $sBaseURL);
166 exec($oCMD->escapedCmd(), $aOutput, $iRet);
167 if ($iRet != 0 || $aOutput[0] == 'None') {
168 fail('Error running pyosmium tools');
171 $oDB->exec('TRUNCATE import_status');
172 $sSQL = "INSERT INTO import_status (lastimportdate, sequence_id, indexed) VALUES('";
173 $sSQL .= $sDatabaseDate."',".$aOutput[0].', true)';
177 } catch (\Nominatim\DatabaseError $e) {
178 fail('Could not enter sequence into database.');
181 echo "Done. Database updates will start at sequence $aOutput[0] ($sWindBack)\n";
184 if ($aResult['check-for-updates']) {
185 $aLastState = $oDB->getRow('SELECT sequence_id FROM import_status');
187 if (!$aLastState['sequence_id']) {
188 fail('Updates not set up. Please run ./utils/update.php --init-updates.');
191 $oCmd = (new \Nominatim\Shell(CONST_BinDir.'/check_server_for_updates.py'))
192 ->addParams($sBaseURL)
193 ->addParams($aLastState['sequence_id']);
194 $iRet = $oCmd->run();
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'];
203 if (!file_exists($sNextFile)) {
204 fail("Cannot open $sNextFile\n");
208 $oCMD = (clone $oOsm2pgsqlCmd)->addParams($sNextFile);
209 echo $oCMD->escapedCmd()."\n";
210 $iRet = $oCMD->run();
213 fail("Error from osm2pgsql, $iRet\n");
216 // Don't update the import status - we don't know what this file contains
219 if ($aResult['calculate-postcodes']) {
220 (clone($oNominatimCmd))->addParams('refresh', '--postcodes')->run();
223 $sTemporaryFile = CONST_InstallDir.'/osmosischange.osc';
225 $bUseOSMApi = isset($aResult['import-from-main-api']) && $aResult['import-from-main-api'];
227 if (isset($aResult['import-node']) && $aResult['import-node']) {
229 $sContentURL = 'https://www.openstreetmap.org/api/0.6/node/'.$aResult['import-node'];
231 $sContentURL = 'https://overpass-api.de/api/interpreter?data=node('.$aResult['import-node'].');out%20meta;';
235 if (isset($aResult['import-way']) && $aResult['import-way']) {
237 $sContentURL = 'https://www.openstreetmap.org/api/0.6/way/'.$aResult['import-way'].'/full';
239 $sContentURL = 'https://overpass-api.de/api/interpreter?data=(way('.$aResult['import-way'].');%3E;);out%20meta;';
243 if (isset($aResult['import-relation']) && $aResult['import-relation']) {
245 $sContentURL = 'https://www.openstreetmap.org/api/0.6/relation/'.$aResult['import-relation'].'/full';
247 $sContentURL = 'https://overpass-api.de/api/interpreter?data=(rel(id:'.$aResult['import-relation'].');%3E;);out%20meta;';
252 file_put_contents($sTemporaryFile, file_get_contents($sContentURL));
257 // import generated change file
259 $oCMD = (clone $oOsm2pgsqlCmd)->addParams($sTemporaryFile);
260 echo $oCMD->escapedCmd()."\n";
262 $iRet = $oCMD->run();
264 fail("osm2pgsql exited with error level $iRet\n");
268 if ($aResult['recompute-word-counts']) {
269 (clone($oNominatimCmd))->addParams('refresh', '--word-counts')->run();
272 if ($aResult['index']) {
273 (clone $oNominatimCmd)->addParams('index', '--minrank', $aResult['index-rank'])->run();
276 if ($aResult['update-address-levels']) {
277 (clone($oNominatimCmd))->addParams('refresh', '--address-levels')->run();
280 if ($aResult['recompute-importance']) {
281 echo "Updating importance values for database.\n";
282 $oDB = new Nominatim\DB();
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;';
297 if ($aResult['import-osmosis'] || $aResult['import-osmosis-all']) {
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");
304 $sImportFile = CONST_InstallDir.'/osmosischange.osc';
306 $oCMDDownload = (new \Nominatim\Shell($sPyosmiumBin))
307 ->addParams('--server', $sBaseURL)
308 ->addParams('--outfile', $sImportFile)
309 ->addParams('--size', getSetting('REPLICATION_MAX_DIFF'));
311 $oCMDImport = (clone $oOsm2pgsqlCmd)->addParams($sImportFile);
314 $fStartTime = time();
315 $aLastState = $oDB->getRow('SELECT *, EXTRACT (EPOCH FROM lastimportdate) as unix_ts FROM import_status');
317 if (!$aLastState['sequence_id']) {
318 echo "Updates not set up. Please run ./utils/update.php --init-updates.\n";
322 echo 'Currently at sequence '.$aLastState['sequence_id'].' ('.$aLastState['lastimportdate'].') - '.$aLastState['indexed']." indexed\n";
324 $sBatchEnd = $aLastState['lastimportdate'];
325 $iEndSequence = $aLastState['sequence_id'];
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.";
336 // Download the next batch of changes.
338 $fCMDStartTime = time();
339 $iNextSeq = (int) $aLastState['sequence_id'];
342 $oCMD = (clone $oCMDDownload)->addParams('--start-id', $iNextSeq);
343 echo $oCMD->escapedCmd()."\n";
344 if (file_exists($sImportFile)) {
345 unlink($sImportFile);
347 exec($oCMD->escapedCmd(), $aOutput, $iResult);
350 $sSleep = getSetting('REPLICATION_RECHECK_INTERVAL');
351 echo 'No new updates. Sleeping for '.$sSleep." sec.\n";
353 } elseif ($iResult != 0) {
354 echo 'ERROR: updates failed.';
357 $iEndSequence = (int)$aOutput[0];
361 // get the newest object from the diff file
364 $oCMD = new \Nominatim\Shell(CONST_BinDir.'/osm_file_date.py', $sImportFile);
365 exec($oCMD->escapedCmd(), $sBatchEnd, $iRet);
367 echo "Diff file is empty. skipping import.\n";
368 if (!$aResult['import-osmosis-all']) {
375 fail('Error getting date from diff file.');
377 $sBatchEnd = $sBatchEnd[0];
380 $fCMDStartTime = time();
383 echo $oCMDImport->escapedCmd()."\n";
385 $iErrorLevel = $oCMDImport->run();
387 echo "Error executing osm2pgsql: $iErrorLevel\n";
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')";
402 $sSQL = "UPDATE import_status SET lastimportdate = '$sBatchEnd', indexed=false, sequence_id = $iEndSequence";
405 echo date('Y-m-d H:i:s')." Completed download step for $sBatchEnd in ".round((time()-$fCMDStartTime)/60, 2)." minutes\n";
409 if (!$aResult['no-index']) {
410 $fCMDStartTime = time();
412 $oThisIndexCmd = clone($oNominatimCmd);
413 $oThisIndexCmd->addParams('index');
414 echo $oThisIndexCmd->escapedCmd()."\n";
415 $iErrorLevel = $oThisIndexCmd->run();
417 echo "Error: $iErrorLevel\n";
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')";
428 echo date('Y-m-d H:i:s')." Completed index step for $sBatchEnd in ".round((time()-$fCMDStartTime)/60, 2)." minutes\n";
430 if ($aResult['import-osmosis-all']) {
431 echo "Error: --no-index cannot be used with continuous imports (--import-osmosis-all).\n";
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);