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