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