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