]> 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']);
283
284     // echo $oCmd->escapedCmd()."\n";
285     $oCmd->run();
286
287     $oDB->exec('update import_status set indexed = true');
288 }
289
290 if ($aResult['update-address-levels']) {
291     echo 'Updating address levels from '.CONST_Address_Level_Config.".\n";
292     $oAlParser = new \Nominatim\Setup\AddressLevelParser(CONST_Address_Level_Config);
293     $oAlParser->createTable($oDB, 'address_levels');
294 }
295
296 if ($aResult['recompute-importance']) {
297     echo "Updating importance values for database.\n";
298     $oDB = new Nominatim\DB();
299     $oDB->connect();
300
301     $sSQL = 'ALTER TABLE placex DISABLE TRIGGER ALL;';
302     $sSQL .= 'UPDATE placex SET (wikipedia, importance) =';
303     $sSQL .= '   (SELECT wikipedia, importance';
304     $sSQL .= '    FROM compute_importance(extratags, country_code, osm_type, osm_id));';
305     $sSQL .= 'UPDATE placex s SET wikipedia = d.wikipedia, importance = d.importance';
306     $sSQL .= ' FROM placex d';
307     $sSQL .= ' WHERE s.place_id = d.linked_place_id and d.wikipedia is not null';
308     $sSQL .= '       and (s.wikipedia is null or s.importance < d.importance);';
309     $sSQL .= 'ALTER TABLE placex ENABLE TRIGGER ALL;';
310     $oDB->exec($sSQL);
311 }
312
313 if ($aResult['import-osmosis'] || $aResult['import-osmosis-all']) {
314     //
315     if (strpos(CONST_Replication_Url, 'download.geofabrik.de') !== false && CONST_Replication_Update_Interval < 86400) {
316         fail('Error: Update interval too low for download.geofabrik.de. ' .
317              "Please check install documentation (https://nominatim.org/release-docs/latest/admin/Import-and-Update#setting-up-the-update-process)\n");
318     }
319
320     $sImportFile = CONST_InstallPath.'/osmosischange.osc';
321
322     $oCMDDownload = (new \Nominatim\Shell(CONST_Pyosmium_Binary))
323                     ->addParams('--server', CONST_Replication_Url)
324                     ->addParams('--outfile', $sImportFile)
325                     ->addParams('--size', CONST_Replication_Max_Diff_size);
326
327     $oCMDImport = (clone $oOsm2pgsqlCmd)->addParams($sImportFile);
328
329     while (true) {
330         $fStartTime = time();
331         $aLastState = $oDB->getRow('SELECT *, EXTRACT (EPOCH FROM lastimportdate) as unix_ts FROM import_status');
332
333         if (!$aLastState['sequence_id']) {
334             echo "Updates not set up. Please run ./utils/update.php --init-updates.\n";
335             exit(1);
336         }
337
338         echo 'Currently at sequence '.$aLastState['sequence_id'].' ('.$aLastState['lastimportdate'].') - '.$aLastState['indexed']." indexed\n";
339
340         $sBatchEnd = $aLastState['lastimportdate'];
341         $iEndSequence = $aLastState['sequence_id'];
342
343         if ($aLastState['indexed']) {
344             // Sleep if the update interval has not yet been reached.
345             $fNextUpdate = $aLastState['unix_ts'] + CONST_Replication_Update_Interval;
346             if ($fNextUpdate > $fStartTime) {
347                 $iSleepTime = $fNextUpdate - $fStartTime;
348                 echo "Waiting for next update for $iSleepTime sec.";
349                 sleep($iSleepTime);
350             }
351
352             // Download the next batch of changes.
353             do {
354                 $fCMDStartTime = time();
355                 $iNextSeq = (int) $aLastState['sequence_id'];
356                 unset($aOutput);
357
358                 $oCMD = (clone $oCMDDownload)->addParams('--start-id', $iNextSeq);
359                 echo $oCMD->escapedCmd()."\n";
360                 if (file_exists($sImportFile)) {
361                     unlink($sImportFile);
362                 }
363                 exec($oCMD->escapedCmd(), $aOutput, $iResult);
364
365                 if ($iResult == 3) {
366                     echo 'No new updates. Sleeping for '.CONST_Replication_Recheck_Interval." sec.\n";
367                     sleep(CONST_Replication_Recheck_Interval);
368                 } elseif ($iResult != 0) {
369                     echo 'ERROR: updates failed.';
370                     exit($iResult);
371                 } else {
372                     $iEndSequence = (int)$aOutput[0];
373                 }
374             } while ($iResult);
375
376             // get the newest object from the diff file
377             $sBatchEnd = 0;
378             $iRet = 0;
379             $oCMD = new \Nominatim\Shell(CONST_BasePath.'/utils/osm_file_date.py', $sImportFile);
380             exec($oCMD->escapedCmd(), $sBatchEnd, $iRet);
381             if ($iRet == 5) {
382                 echo "Diff file is empty. skipping import.\n";
383                 if (!$aResult['import-osmosis-all']) {
384                     exit(0);
385                 } else {
386                     continue;
387                 }
388             }
389             if ($iRet != 0) {
390                 fail('Error getting date from diff file.');
391             }
392             $sBatchEnd = $sBatchEnd[0];
393
394             // Import the file
395             $fCMDStartTime = time();
396
397
398             echo $oCMDImport->escapedCmd()."\n";
399             unset($sJunk);
400             $iErrorLevel = $oCMDImport->run();
401             if ($iErrorLevel) {
402                 echo "Error executing osm2pgsql: $iErrorLevel\n";
403                 exit($iErrorLevel);
404             }
405
406             // write the update logs
407             $iFileSize = filesize($sImportFile);
408             $sSQL = 'INSERT INTO import_osmosis_log';
409             $sSQL .= '(batchend, batchseq, batchsize, starttime, endtime, event)';
410             $sSQL .= " values ('$sBatchEnd',$iEndSequence,$iFileSize,'";
411             $sSQL .= date('Y-m-d H:i:s', $fCMDStartTime)."','";
412             $sSQL .= date('Y-m-d H:i:s')."','import')";
413             var_Dump($sSQL);
414             $oDB->exec($sSQL);
415
416             // update the status
417             $sSQL = "UPDATE import_status SET lastimportdate = '$sBatchEnd', indexed=false, sequence_id = $iEndSequence";
418             var_Dump($sSQL);
419             $oDB->exec($sSQL);
420             echo date('Y-m-d H:i:s')." Completed download step for $sBatchEnd in ".round((time()-$fCMDStartTime)/60, 2)." minutes\n";
421         }
422
423         // Index file
424         if (!$aResult['no-index']) {
425             $oThisIndexCmd = clone($oIndexCmd);
426             $fCMDStartTime = time();
427
428             echo $oThisIndexCmd->escapedCmd()."\n";
429             $iErrorLevel = $oThisIndexCmd->run();
430             if ($iErrorLevel) {
431                 echo "Error: $iErrorLevel\n";
432                 exit($iErrorLevel);
433             }
434
435             $sSQL = 'INSERT INTO import_osmosis_log';
436             $sSQL .= '(batchend, batchseq, batchsize, starttime, endtime, event)';
437             $sSQL .= " values ('$sBatchEnd',$iEndSequence,NULL,'";
438             $sSQL .= date('Y-m-d H:i:s', $fCMDStartTime)."','";
439             $sSQL .= date('Y-m-d H:i:s')."','index')";
440             var_Dump($sSQL);
441             $oDB->exec($sSQL);
442             echo date('Y-m-d H:i:s')." Completed index step for $sBatchEnd in ".round((time()-$fCMDStartTime)/60, 2)." minutes\n";
443
444             $sSQL = 'update import_status set indexed = true';
445             $oDB->exec($sSQL);
446         } else {
447             if ($aResult['import-osmosis-all']) {
448                 echo "Error: --no-index cannot be used with continuous imports (--import-osmosis-all).\n";
449                 exit(1);
450             }
451         }
452
453         $fDuration = time() - $fStartTime;
454         echo date('Y-m-d H:i:s')." Completed all for $sBatchEnd in ".round($fDuration/60, 2)." minutes\n";
455         if (!$aResult['import-osmosis-all']) exit(0);
456     }
457 }