]> git.openstreetmap.org Git - nominatim.git/blob - lib/admin/update.php
convert functon creation to python
[nominatim.git] / lib / admin / update.php
1 <?php
2 @define('CONST_LibDir', dirname(dirname(__FILE__)));
3
4 require_once(CONST_LibDir.'/init-cmd.php');
5 require_once(CONST_LibDir.'/setup_functions.php');
6 require_once(CONST_LibDir.'/setup/SetupClass.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    array('project-dir', '', 0, 1, 1, 1, 'realpath', 'Base directory of the Nominatim installation (default: .)'),
47   );
48
49 getCmdOpt($_SERVER['argv'], $aCMDOptions, $aResult, true, true);
50
51 loadSettings($aCMDResult['project-dir'] ?? getcwd());
52 setupHTTPProxy();
53
54 if (!isset($aResult['index-instances'])) $aResult['index-instances'] = 1;
55 if (!isset($aResult['index-rank'])) $aResult['index-rank'] = 0;
56
57 date_default_timezone_set('Etc/UTC');
58
59 $oDB = new Nominatim\DB();
60 $oDB->connect();
61 $fPostgresVersion = $oDB->getPostgresVersion();
62
63 $aDSNInfo = Nominatim\DB::parseDSN(getSetting('DATABASE_DSN'));
64 if (!isset($aDSNInfo['port']) || !$aDSNInfo['port']) $aDSNInfo['port'] = 5432;
65
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";
71 }
72
73 $oOsm2pgsqlCmd = (new \Nominatim\Shell(getOsm2pgsqlBinary()))
74                  ->addParams('--hstore')
75                  ->addParams('--latlong')
76                  ->addParams('--append')
77                  ->addParams('--slim')
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']);
86
87 if (isset($aDSNInfo['hostspec']) && $aDSNInfo['hostspec']) {
88     $oOsm2pgsqlCmd->addParams('--host', $aDSNInfo['hostspec']);
89 }
90 if (isset($aDSNInfo['username']) && $aDSNInfo['username']) {
91     $oOsm2pgsqlCmd->addParams('--user', $aDSNInfo['username']);
92 }
93 if (isset($aDSNInfo['password']) && $aDSNInfo['password']) {
94     $oOsm2pgsqlCmd->addEnvPair('PGPASSWORD', $aDSNInfo['password']);
95 }
96 if (getSetting('FLATNODE_FILE')) {
97     $oOsm2pgsqlCmd->addParams('--flat-nodes', getSetting('FLATNODE_FILE'));
98 }
99 if ($fPostgresVersion >= 11.0) {
100     $oOsm2pgsqlCmd->addEnvPair(
101         'PGOPTIONS',
102         '-c jit=off -c max_parallel_workers_per_gather=0'
103     );
104 }
105
106 $oNominatimCmd = new \Nominatim\Shell(getSetting('NOMINATIM_TOOL'));
107 if ($aResult['quiet']) {
108     $oNominatimCmd->addParams('--quiet');
109 }
110 if ($aResult['verbose']) {
111     $oNominatimCmd->addParams('--verbose');
112 }
113
114 $sPyosmiumBin = getSetting('PYOSMIUM_BINARY');
115 $sBaseURL = getSetting('REPLICATION_URL');
116
117
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.');
125     }
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');
132     }
133
134     $aOutput = 0;
135     $oCMD = new \Nominatim\Shell($sPyosmiumBin, '--help');
136     exec($oCMD->escapedCmd(), $aOutput, $iRet);
137
138     if ($iRet != 0) {
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');
143     }
144
145     if (!$aResult['no-update-functions']) {
146         (clone($oNominatimCmd))->addParams('refresh', '--functions')->run();
147     }
148
149     $sDatabaseDate = getDatabaseDate($oDB);
150     if (!$sDatabaseDate) {
151         fail('Cannot determine date of database.');
152     }
153     $sWindBack = strftime('%Y-%m-%dT%H:%M:%SZ', strtotime($sDatabaseDate) - (3*60*60));
154
155     // get the appropriate state id
156     $aOutput = 0;
157     $oCMD = (new \Nominatim\Shell($sPyosmiumBin))
158             ->addParams('--start-date', $sWindBack)
159             ->addParams('--server', $sBaseURL);
160
161     exec($oCMD->escapedCmd(), $aOutput, $iRet);
162     if ($iRet != 0 || $aOutput[0] == 'None') {
163         fail('Error running pyosmium tools');
164     }
165
166     $oDB->exec('TRUNCATE import_status');
167     $sSQL = "INSERT INTO import_status (lastimportdate, sequence_id, indexed) VALUES('";
168     $sSQL .= $sDatabaseDate."',".$aOutput[0].', true)';
169
170     try {
171         $oDB->exec($sSQL);
172     } catch (\Nominatim\DatabaseError $e) {
173         fail('Could not enter sequence into database.');
174     }
175
176     echo "Done. Database updates will start at sequence $aOutput[0] ($sWindBack)\n";
177 }
178
179 if ($aResult['check-for-updates']) {
180     $aLastState = $oDB->getRow('SELECT sequence_id FROM import_status');
181
182     if (!$aLastState['sequence_id']) {
183         fail('Updates not set up. Please run ./utils/update.php --init-updates.');
184     }
185
186     $oCmd = (new \Nominatim\Shell(CONST_BinDir.'/check_server_for_updates.py'))
187             ->addParams($sBaseURL)
188             ->addParams($aLastState['sequence_id']);
189     $iRet = $oCmd->run();
190
191     exit($iRet);
192 }
193
194 if (isset($aResult['import-diff']) || isset($aResult['import-file'])) {
195     // import diffs and files directly (e.g. from osmosis --rri)
196     $sNextFile = isset($aResult['import-diff']) ? $aResult['import-diff'] : $aResult['import-file'];
197
198     if (!file_exists($sNextFile)) {
199         fail("Cannot open $sNextFile\n");
200     }
201
202     // Import the file
203     $oCMD = (clone $oOsm2pgsqlCmd)->addParams($sNextFile);
204     echo $oCMD->escapedCmd()."\n";
205     $iRet = $oCMD->run();
206
207     if ($iRet) {
208         fail("Error from osm2pgsql, $iRet\n");
209     }
210
211     // Don't update the import status - we don't know what this file contains
212 }
213
214 if ($aResult['calculate-postcodes']) {
215     (clone($oNominatimCmd))->addParams('refresh', '--postcodes')->run();
216 }
217
218 $sTemporaryFile = CONST_InstallDir.'/osmosischange.osc';
219 $bHaveDiff = false;
220 $bUseOSMApi = isset($aResult['import-from-main-api']) && $aResult['import-from-main-api'];
221 $sContentURL = '';
222 if (isset($aResult['import-node']) && $aResult['import-node']) {
223     if ($bUseOSMApi) {
224         $sContentURL = 'https://www.openstreetmap.org/api/0.6/node/'.$aResult['import-node'];
225     } else {
226         $sContentURL = 'https://overpass-api.de/api/interpreter?data=node('.$aResult['import-node'].');out%20meta;';
227     }
228 }
229
230 if (isset($aResult['import-way']) && $aResult['import-way']) {
231     if ($bUseOSMApi) {
232         $sContentURL = 'https://www.openstreetmap.org/api/0.6/way/'.$aResult['import-way'].'/full';
233     } else {
234         $sContentURL = 'https://overpass-api.de/api/interpreter?data=(way('.$aResult['import-way'].');%3E;);out%20meta;';
235     }
236 }
237
238 if (isset($aResult['import-relation']) && $aResult['import-relation']) {
239     if ($bUseOSMApi) {
240         $sContentURL = 'https://www.openstreetmap.org/api/0.6/relation/'.$aResult['import-relation'].'/full';
241     } else {
242         $sContentURL = 'https://overpass-api.de/api/interpreter?data=(rel(id:'.$aResult['import-relation'].');%3E;);out%20meta;';
243     }
244 }
245
246 if ($sContentURL) {
247     file_put_contents($sTemporaryFile, file_get_contents($sContentURL));
248     $bHaveDiff = true;
249 }
250
251 if ($bHaveDiff) {
252     // import generated change file
253
254     $oCMD = (clone $oOsm2pgsqlCmd)->addParams($sTemporaryFile);
255     echo $oCMD->escapedCmd()."\n";
256
257     $iRet = $oCMD->run();
258     if ($iRet) {
259         fail("osm2pgsql exited with error level $iRet\n");
260     }
261 }
262
263 if ($aResult['recompute-word-counts']) {
264     (clone($oNominatimCmd))->addParams('refresh', '--word-counts')->run();
265 }
266
267 if ($aResult['index']) {
268     (clone $oNominatimCmd)->addParams('index', '--minrank', $aResult['index-rank'])->run();
269 }
270
271 if ($aResult['update-address-levels']) {
272     (clone($oNominatimCmd))->addParams('refresh', '--address-levels')->run();
273 }
274
275 if ($aResult['recompute-importance']) {
276     echo "Updating importance values for database.\n";
277     $oDB = new Nominatim\DB();
278     $oDB->connect();
279
280     $sSQL = 'ALTER TABLE placex DISABLE TRIGGER ALL;';
281     $sSQL .= 'UPDATE placex SET (wikipedia, importance) =';
282     $sSQL .= '   (SELECT wikipedia, importance';
283     $sSQL .= '    FROM compute_importance(extratags, country_code, osm_type, osm_id));';
284     $sSQL .= 'UPDATE placex s SET wikipedia = d.wikipedia, importance = d.importance';
285     $sSQL .= ' FROM placex d';
286     $sSQL .= ' WHERE s.place_id = d.linked_place_id and d.wikipedia is not null';
287     $sSQL .= '       and (s.wikipedia is null or s.importance < d.importance);';
288     $sSQL .= 'ALTER TABLE placex ENABLE TRIGGER ALL;';
289     $oDB->exec($sSQL);
290 }
291
292 if ($aResult['import-osmosis'] || $aResult['import-osmosis-all']) {
293     //
294     if (strpos($sBaseURL, 'download.geofabrik.de') !== false && getSetting('REPLICATION_UPDATE_INTERVAL') < 86400) {
295         fail('Error: Update interval too low for download.geofabrik.de. ' .
296              "Please check install documentation (https://nominatim.org/release-docs/latest/admin/Import-and-Update#setting-up-the-update-process)\n");
297     }
298
299     $sImportFile = CONST_InstallDir.'/osmosischange.osc';
300
301     $oCMDDownload = (new \Nominatim\Shell($sPyosmiumBin))
302                     ->addParams('--server', $sBaseURL)
303                     ->addParams('--outfile', $sImportFile)
304                     ->addParams('--size', getSetting('REPLICATION_MAX_DIFF'));
305
306     $oCMDImport = (clone $oOsm2pgsqlCmd)->addParams($sImportFile);
307
308     while (true) {
309         $fStartTime = time();
310         $aLastState = $oDB->getRow('SELECT *, EXTRACT (EPOCH FROM lastimportdate) as unix_ts FROM import_status');
311
312         if (!$aLastState['sequence_id']) {
313             echo "Updates not set up. Please run ./utils/update.php --init-updates.\n";
314             exit(1);
315         }
316
317         echo 'Currently at sequence '.$aLastState['sequence_id'].' ('.$aLastState['lastimportdate'].') - '.$aLastState['indexed']." indexed\n";
318
319         $sBatchEnd = $aLastState['lastimportdate'];
320         $iEndSequence = $aLastState['sequence_id'];
321
322         if ($aLastState['indexed']) {
323             // Sleep if the update interval has not yet been reached.
324             $fNextUpdate = $aLastState['unix_ts'] + getSetting('REPLICATION_UPDATE_INTERVAL');
325             if ($fNextUpdate > $fStartTime) {
326                 $iSleepTime = $fNextUpdate - $fStartTime;
327                 echo "Waiting for next update for $iSleepTime sec.";
328                 sleep($iSleepTime);
329             }
330
331             // Download the next batch of changes.
332             do {
333                 $fCMDStartTime = time();
334                 $iNextSeq = (int) $aLastState['sequence_id'];
335                 unset($aOutput);
336
337                 $oCMD = (clone $oCMDDownload)->addParams('--start-id', $iNextSeq);
338                 echo $oCMD->escapedCmd()."\n";
339                 if (file_exists($sImportFile)) {
340                     unlink($sImportFile);
341                 }
342                 exec($oCMD->escapedCmd(), $aOutput, $iResult);
343
344                 if ($iResult == 3) {
345                     $sSleep = getSetting('REPLICATION_RECHECK_INTERVAL');
346                     echo 'No new updates. Sleeping for '.$sSleep." sec.\n";
347                     sleep($sSleep);
348                 } elseif ($iResult != 0) {
349                     echo 'ERROR: updates failed.';
350                     exit($iResult);
351                 } else {
352                     $iEndSequence = (int)$aOutput[0];
353                 }
354             } while ($iResult);
355
356             // get the newest object from the diff file
357             $sBatchEnd = 0;
358             $iRet = 0;
359             $oCMD = new \Nominatim\Shell(CONST_BinDir.'/osm_file_date.py', $sImportFile);
360             exec($oCMD->escapedCmd(), $sBatchEnd, $iRet);
361             if ($iRet == 5) {
362                 echo "Diff file is empty. skipping import.\n";
363                 if (!$aResult['import-osmosis-all']) {
364                     exit(0);
365                 } else {
366                     continue;
367                 }
368             }
369             if ($iRet != 0) {
370                 fail('Error getting date from diff file.');
371             }
372             $sBatchEnd = $sBatchEnd[0];
373
374             // Import the file
375             $fCMDStartTime = time();
376
377
378             echo $oCMDImport->escapedCmd()."\n";
379             unset($sJunk);
380             $iErrorLevel = $oCMDImport->run();
381             if ($iErrorLevel) {
382                 echo "Error executing osm2pgsql: $iErrorLevel\n";
383                 exit($iErrorLevel);
384             }
385
386             // write the update logs
387             $iFileSize = filesize($sImportFile);
388             $sSQL = 'INSERT INTO import_osmosis_log';
389             $sSQL .= '(batchend, batchseq, batchsize, starttime, endtime, event)';
390             $sSQL .= " values ('$sBatchEnd',$iEndSequence,$iFileSize,'";
391             $sSQL .= date('Y-m-d H:i:s', $fCMDStartTime)."','";
392             $sSQL .= date('Y-m-d H:i:s')."','import')";
393             var_Dump($sSQL);
394             $oDB->exec($sSQL);
395
396             // update the status
397             $sSQL = "UPDATE import_status SET lastimportdate = '$sBatchEnd', indexed=false, sequence_id = $iEndSequence";
398             var_Dump($sSQL);
399             $oDB->exec($sSQL);
400             echo date('Y-m-d H:i:s')." Completed download step for $sBatchEnd in ".round((time()-$fCMDStartTime)/60, 2)." minutes\n";
401         }
402
403         // Index file
404         if (!$aResult['no-index']) {
405             $fCMDStartTime = time();
406
407             $oThisIndexCmd = clone($oNominatimCmd);
408             $oThisIndexCmd->addParams('index');
409             echo $oThisIndexCmd->escapedCmd()."\n";
410             $iErrorLevel = $oThisIndexCmd->run();
411             if ($iErrorLevel) {
412                 echo "Error: $iErrorLevel\n";
413                 exit($iErrorLevel);
414             }
415
416             $sSQL = 'INSERT INTO import_osmosis_log';
417             $sSQL .= '(batchend, batchseq, batchsize, starttime, endtime, event)';
418             $sSQL .= " values ('$sBatchEnd',$iEndSequence,NULL,'";
419             $sSQL .= date('Y-m-d H:i:s', $fCMDStartTime)."','";
420             $sSQL .= date('Y-m-d H:i:s')."','index')";
421             var_Dump($sSQL);
422             $oDB->exec($sSQL);
423             echo date('Y-m-d H:i:s')." Completed index step for $sBatchEnd in ".round((time()-$fCMDStartTime)/60, 2)." minutes\n";
424         } else {
425             if ($aResult['import-osmosis-all']) {
426                 echo "Error: --no-index cannot be used with continuous imports (--import-osmosis-all).\n";
427                 exit(1);
428             }
429         }
430
431         $fDuration = time() - $fStartTime;
432         echo date('Y-m-d H:i:s')." Completed all for $sBatchEnd in ".round($fDuration/60, 2)." minutes\n";
433         if (!$aResult['import-osmosis-all']) exit(0);
434     }
435 }