]> git.openstreetmap.org Git - nominatim.git/blob - lib/admin/update.php
port check-for-update function 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     $oCmd = (clone($oNominatimCmd))->addParams('replication', '--init');
120
121     if ($aResult['no-update-functions']) {
122         $oCmd->addParams('--no-update-functions');
123     }
124
125     $oCmd->run();
126 }
127
128 if ($aResult['check-for-updates']) {
129     exit((clone($oNominatimCmd))->addParams('replication', '--check-for-updates')->run());
130 }
131
132 if (isset($aResult['import-diff']) || isset($aResult['import-file'])) {
133     // import diffs and files directly (e.g. from osmosis --rri)
134     $sNextFile = isset($aResult['import-diff']) ? $aResult['import-diff'] : $aResult['import-file'];
135
136     if (!file_exists($sNextFile)) {
137         fail("Cannot open $sNextFile\n");
138     }
139
140     // Import the file
141     $oCMD = (clone $oOsm2pgsqlCmd)->addParams($sNextFile);
142     echo $oCMD->escapedCmd()."\n";
143     $iRet = $oCMD->run();
144
145     if ($iRet) {
146         fail("Error from osm2pgsql, $iRet\n");
147     }
148
149     // Don't update the import status - we don't know what this file contains
150 }
151
152 if ($aResult['calculate-postcodes']) {
153     (clone($oNominatimCmd))->addParams('refresh', '--postcodes')->run();
154 }
155
156 $sTemporaryFile = CONST_InstallDir.'/osmosischange.osc';
157 $bHaveDiff = false;
158 $bUseOSMApi = isset($aResult['import-from-main-api']) && $aResult['import-from-main-api'];
159 $sContentURL = '';
160 if (isset($aResult['import-node']) && $aResult['import-node']) {
161     if ($bUseOSMApi) {
162         $sContentURL = 'https://www.openstreetmap.org/api/0.6/node/'.$aResult['import-node'];
163     } else {
164         $sContentURL = 'https://overpass-api.de/api/interpreter?data=node('.$aResult['import-node'].');out%20meta;';
165     }
166 }
167
168 if (isset($aResult['import-way']) && $aResult['import-way']) {
169     if ($bUseOSMApi) {
170         $sContentURL = 'https://www.openstreetmap.org/api/0.6/way/'.$aResult['import-way'].'/full';
171     } else {
172         $sContentURL = 'https://overpass-api.de/api/interpreter?data=(way('.$aResult['import-way'].');%3E;);out%20meta;';
173     }
174 }
175
176 if (isset($aResult['import-relation']) && $aResult['import-relation']) {
177     if ($bUseOSMApi) {
178         $sContentURL = 'https://www.openstreetmap.org/api/0.6/relation/'.$aResult['import-relation'].'/full';
179     } else {
180         $sContentURL = 'https://overpass-api.de/api/interpreter?data=(rel(id:'.$aResult['import-relation'].');%3E;);out%20meta;';
181     }
182 }
183
184 if ($sContentURL) {
185     file_put_contents($sTemporaryFile, file_get_contents($sContentURL));
186     $bHaveDiff = true;
187 }
188
189 if ($bHaveDiff) {
190     // import generated change file
191
192     $oCMD = (clone $oOsm2pgsqlCmd)->addParams($sTemporaryFile);
193     echo $oCMD->escapedCmd()."\n";
194
195     $iRet = $oCMD->run();
196     if ($iRet) {
197         fail("osm2pgsql exited with error level $iRet\n");
198     }
199 }
200
201 if ($aResult['recompute-word-counts']) {
202     (clone($oNominatimCmd))->addParams('refresh', '--word-counts')->run();
203 }
204
205 if ($aResult['index']) {
206     (clone $oNominatimCmd)->addParams('index', '--minrank', $aResult['index-rank'])->run();
207 }
208
209 if ($aResult['update-address-levels']) {
210     (clone($oNominatimCmd))->addParams('refresh', '--address-levels')->run();
211 }
212
213 if ($aResult['recompute-importance']) {
214     echo "Updating importance values for database.\n";
215     $oDB = new Nominatim\DB();
216     $oDB->connect();
217
218     $sSQL = 'ALTER TABLE placex DISABLE TRIGGER ALL;';
219     $sSQL .= 'UPDATE placex SET (wikipedia, importance) =';
220     $sSQL .= '   (SELECT wikipedia, importance';
221     $sSQL .= '    FROM compute_importance(extratags, country_code, osm_type, osm_id));';
222     $sSQL .= 'UPDATE placex s SET wikipedia = d.wikipedia, importance = d.importance';
223     $sSQL .= ' FROM placex d';
224     $sSQL .= ' WHERE s.place_id = d.linked_place_id and d.wikipedia is not null';
225     $sSQL .= '       and (s.wikipedia is null or s.importance < d.importance);';
226     $sSQL .= 'ALTER TABLE placex ENABLE TRIGGER ALL;';
227     $oDB->exec($sSQL);
228 }
229
230 if ($aResult['import-osmosis'] || $aResult['import-osmosis-all']) {
231     //
232     if (strpos($sBaseURL, 'download.geofabrik.de') !== false && getSetting('REPLICATION_UPDATE_INTERVAL') < 86400) {
233         fail('Error: Update interval too low for download.geofabrik.de. ' .
234              "Please check install documentation (https://nominatim.org/release-docs/latest/admin/Import-and-Update#setting-up-the-update-process)\n");
235     }
236
237     $sImportFile = CONST_InstallDir.'/osmosischange.osc';
238
239     $oCMDDownload = (new \Nominatim\Shell($sPyosmiumBin))
240                     ->addParams('--server', $sBaseURL)
241                     ->addParams('--outfile', $sImportFile)
242                     ->addParams('--size', getSetting('REPLICATION_MAX_DIFF'));
243
244     $oCMDImport = (clone $oOsm2pgsqlCmd)->addParams($sImportFile);
245
246     while (true) {
247         $fStartTime = time();
248         $aLastState = $oDB->getRow('SELECT *, EXTRACT (EPOCH FROM lastimportdate) as unix_ts FROM import_status');
249
250         if (!$aLastState['sequence_id']) {
251             echo "Updates not set up. Please run ./utils/update.php --init-updates.\n";
252             exit(1);
253         }
254
255         echo 'Currently at sequence '.$aLastState['sequence_id'].' ('.$aLastState['lastimportdate'].') - '.$aLastState['indexed']." indexed\n";
256
257         $sBatchEnd = $aLastState['lastimportdate'];
258         $iEndSequence = $aLastState['sequence_id'];
259
260         if ($aLastState['indexed']) {
261             // Sleep if the update interval has not yet been reached.
262             $fNextUpdate = $aLastState['unix_ts'] + getSetting('REPLICATION_UPDATE_INTERVAL');
263             if ($fNextUpdate > $fStartTime) {
264                 $iSleepTime = $fNextUpdate - $fStartTime;
265                 echo "Waiting for next update for $iSleepTime sec.";
266                 sleep($iSleepTime);
267             }
268
269             // Download the next batch of changes.
270             do {
271                 $fCMDStartTime = time();
272                 $iNextSeq = (int) $aLastState['sequence_id'];
273                 unset($aOutput);
274
275                 $oCMD = (clone $oCMDDownload)->addParams('--start-id', $iNextSeq);
276                 echo $oCMD->escapedCmd()."\n";
277                 if (file_exists($sImportFile)) {
278                     unlink($sImportFile);
279                 }
280                 exec($oCMD->escapedCmd(), $aOutput, $iResult);
281
282                 if ($iResult == 3) {
283                     $sSleep = getSetting('REPLICATION_RECHECK_INTERVAL');
284                     echo 'No new updates. Sleeping for '.$sSleep." sec.\n";
285                     sleep($sSleep);
286                 } elseif ($iResult != 0) {
287                     echo 'ERROR: updates failed.';
288                     exit($iResult);
289                 } else {
290                     $iEndSequence = (int)$aOutput[0];
291                 }
292             } while ($iResult);
293
294             // get the newest object from the diff file
295             $sBatchEnd = 0;
296             $iRet = 0;
297             $oCMD = new \Nominatim\Shell(CONST_BinDir.'/osm_file_date.py', $sImportFile);
298             exec($oCMD->escapedCmd(), $sBatchEnd, $iRet);
299             if ($iRet == 5) {
300                 echo "Diff file is empty. skipping import.\n";
301                 if (!$aResult['import-osmosis-all']) {
302                     exit(0);
303                 } else {
304                     continue;
305                 }
306             }
307             if ($iRet != 0) {
308                 fail('Error getting date from diff file.');
309             }
310             $sBatchEnd = $sBatchEnd[0];
311
312             // Import the file
313             $fCMDStartTime = time();
314
315
316             echo $oCMDImport->escapedCmd()."\n";
317             unset($sJunk);
318             $iErrorLevel = $oCMDImport->run();
319             if ($iErrorLevel) {
320                 echo "Error executing osm2pgsql: $iErrorLevel\n";
321                 exit($iErrorLevel);
322             }
323
324             // write the update logs
325             $iFileSize = filesize($sImportFile);
326             $sSQL = 'INSERT INTO import_osmosis_log';
327             $sSQL .= '(batchend, batchseq, batchsize, starttime, endtime, event)';
328             $sSQL .= " values ('$sBatchEnd',$iEndSequence,$iFileSize,'";
329             $sSQL .= date('Y-m-d H:i:s', $fCMDStartTime)."','";
330             $sSQL .= date('Y-m-d H:i:s')."','import')";
331             var_Dump($sSQL);
332             $oDB->exec($sSQL);
333
334             // update the status
335             $sSQL = "UPDATE import_status SET lastimportdate = '$sBatchEnd', indexed=false, sequence_id = $iEndSequence";
336             var_Dump($sSQL);
337             $oDB->exec($sSQL);
338             echo date('Y-m-d H:i:s')." Completed download step for $sBatchEnd in ".round((time()-$fCMDStartTime)/60, 2)." minutes\n";
339         }
340
341         // Index file
342         if (!$aResult['no-index']) {
343             $fCMDStartTime = time();
344
345             $oThisIndexCmd = clone($oNominatimCmd);
346             $oThisIndexCmd->addParams('index');
347             echo $oThisIndexCmd->escapedCmd()."\n";
348             $iErrorLevel = $oThisIndexCmd->run();
349             if ($iErrorLevel) {
350                 echo "Error: $iErrorLevel\n";
351                 exit($iErrorLevel);
352             }
353
354             $sSQL = 'INSERT INTO import_osmosis_log';
355             $sSQL .= '(batchend, batchseq, batchsize, starttime, endtime, event)';
356             $sSQL .= " values ('$sBatchEnd',$iEndSequence,NULL,'";
357             $sSQL .= date('Y-m-d H:i:s', $fCMDStartTime)."','";
358             $sSQL .= date('Y-m-d H:i:s')."','index')";
359             var_Dump($sSQL);
360             $oDB->exec($sSQL);
361             echo date('Y-m-d H:i:s')." Completed index step for $sBatchEnd in ".round((time()-$fCMDStartTime)/60, 2)." minutes\n";
362         } else {
363             if ($aResult['import-osmosis-all']) {
364                 echo "Error: --no-index cannot be used with continuous imports (--import-osmosis-all).\n";
365                 exit(1);
366             }
367         }
368
369         $fDuration = time() - $fStartTime;
370         echo date('Y-m-d H:i:s')." Completed all for $sBatchEnd in ".round($fDuration/60, 2)." minutes\n";
371         if (!$aResult['import-osmosis-all']) exit(0);
372     }
373 }