]> git.openstreetmap.org Git - nominatim.git/blob - utils/update.php
Merge remote-tracking branch 'upstream/master'
[nominatim.git] / utils / update.php
1 #!/usr/bin/php -Cq
2 <?php
3
4 require_once(dirname(dirname(__FILE__)).'/settings/settings.php');
5 require_once(CONST_BasePath.'/lib/init-cmd.php');
6 ini_set('memory_limit', '800M');
7
8 $aCMDOptions
9 = array(
10    'Import / update / index osm data',
11    array('help', 'h', 0, 1, 0, 0, false, 'Show Help'),
12    array('quiet', 'q', 0, 1, 0, 0, 'bool', 'Quiet output'),
13    array('verbose', 'v', 0, 1, 0, 0, 'bool', 'Verbose output'),
14
15    array('init-updates', '', 0, 1, 0, 0, 'bool', 'Set up database for updating'),
16    array('import-osmosis', '', 0, 1, 0, 0, 'bool', 'Import updates once'),
17    array('import-osmosis-all', '', 0, 1, 0, 0, 'bool', 'Import updates forever'),
18    array('no-index', '', 0, 1, 0, 0, 'bool', 'Do not index the new data'),
19
20    array('calculate-postcodes', '', 0, 1, 0, 0, 'bool', 'Update postcode centroid table'),
21
22    array('import-file', '', 0, 1, 1, 1, 'realpath', 'Re-import data from an OSM file'),
23    array('import-diff', '', 0, 1, 1, 1, 'realpath', 'Import a diff (osc) file from local file system'),
24    array('osm2pgsql-cache', '', 0, 1, 1, 1, 'int', 'Cache size used by osm2pgsql'),
25
26    array('import-node', '', 0, 1, 1, 1, 'int', 'Re-import node'),
27    array('import-way', '', 0, 1, 1, 1, 'int', 'Re-import way'),
28    array('import-relation', '', 0, 1, 1, 1, 'int', 'Re-import relation'),
29    array('import-from-main-api', '', 0, 1, 0, 0, 'bool', 'Use OSM API instead of Overpass to download objects'),
30
31    array('index', '', 0, 1, 0, 0, 'bool', 'Index'),
32    array('index-rank', '', 0, 1, 1, 1, 'int', 'Rank to start indexing from'),
33    array('index-instances', '', 0, 1, 1, 1, 'int', 'Number of indexing instances (threads)'),
34
35    array('deduplicate', '', 0, 1, 0, 0, 'bool', 'Deduplicate tokens'),
36    array('no-npi', '', 0, 1, 0, 0, 'bool', '(obsolete)'),
37   );
38 getCmdOpt($_SERVER['argv'], $aCMDOptions, $aResult, true, true);
39
40 if (!isset($aResult['index-instances'])) $aResult['index-instances'] = 1;
41
42 if (!isset($aResult['index-rank'])) $aResult['index-rank'] = 0;
43
44 date_default_timezone_set('Etc/UTC');
45
46 $oDB =& getDB();
47
48 $aDSNInfo = DB::parseDSN(CONST_Database_DSN);
49 if (!isset($aDSNInfo['port']) || !$aDSNInfo['port']) $aDSNInfo['port'] = 5432;
50
51 // cache memory to be used by osm2pgsql, should not be more than the available memory
52 $iCacheMemory = (isset($aResult['osm2pgsql-cache'])?$aResult['osm2pgsql-cache']:2000);
53 if ($iCacheMemory + 500 > getTotalMemoryMB()) {
54     $iCacheMemory = getCacheMemoryMB();
55     echo "WARNING: resetting cache memory to $iCacheMemory\n";
56 }
57 $sOsm2pgsqlCmd = CONST_Osm2pgsql_Binary.' -klas --number-processes 1 -C '.$iCacheMemory.' -O gazetteer -d '.$aDSNInfo['database'].' -P '.$aDSNInfo['port'];
58 if (!is_null(CONST_Osm2pgsql_Flatnode_File)) {
59     $sOsm2pgsqlCmd .= ' --flat-nodes '.CONST_Osm2pgsql_Flatnode_File;
60 }
61
62 if ($aResult['init-updates']) {
63     // sanity check that the replication URL is correct
64     $sBaseState = file_get_contents(CONST_Replication_Url.'/state.txt');
65     if ($sBaseState === false) {
66         echo "\nCannot find state.txt file at the configured replication URL.\n";
67         echo "Does the URL point to a directory containing OSM update data?\n\n";
68         fail('replication URL not reachable.');
69     }
70     $sSetup = CONST_InstallPath.'/utils/setup.php';
71     $iRet = -1;
72     passthru($sSetup.' --create-functions --enable-diff-updates', $iRet);
73     if ($iRet != 0) {
74         fail('Error running setup script');
75     }
76
77     $sDatabaseDate = getDatabaseDate($oDB);
78     if ($sDatabaseDate === false) {
79         fail('Cannot determine date of database.');
80     }
81     $sWindBack = strftime('%Y-%m-%dT%H:%M:%SZ', strtotime($sDatabaseDate) - (3*60*60));
82
83     // get the appropriate state id
84     $aOutput = 0;
85     $sCmd = CONST_Pyosmium_Binary.' -D '.$sWindBack.' --server '.CONST_Replication_Url;
86     exec($sCmd, $aOutput, $iRet);
87     if ($iRet != 0 || $aOutput[0] == 'None') {
88         fail('Error running pyosmium tools');
89     }
90
91     pg_query($oDB->connection, 'TRUNCATE import_status');
92     $sSQL = "INSERT INTO import_status (lastimportdate, sequence_id, indexed) VALUES('";
93     $sSQL .= $sDatabaseDate."',".$aOutput[0].', true)';
94     if (!pg_query($oDB->connection, $sSQL)) {
95         fail('Could not enter sequence into database.');
96     }
97
98     echo "Done. Database updates will start at sequence $aOutput[0] ($sWindBack)\n";
99 }
100
101 if (isset($aResult['import-diff']) || isset($aResult['import-file'])) {
102     // import diffs and files directly (e.g. from osmosis --rri)
103     $sNextFile = isset($aResult['import-diff']) ? $aResult['import-diff'] : $aResult['import-file'];
104
105     if (!file_exists($sNextFile)) {
106         fail("Cannot open $sNextFile\n");
107     }
108
109     // Import the file
110     $sCMD = $sOsm2pgsqlCmd.' '.$sNextFile;
111     echo $sCMD."\n";
112     exec($sCMD, $sJunk, $iErrorLevel);
113
114     if ($iErrorLevel) {
115         fail("Error from osm2pgsql, $iErrorLevel\n");
116     }
117
118     // Don't update the import status - we don't know what this file contains
119 }
120
121 if ($aResult['calculate-postcodes']) {
122     info('Update postcodes centroids');
123     $sTemplate = file_get_contents(CONST_BasePath.'/sql/update-postcodes.sql');
124     runSQLScript($sTemplate, true, true);
125 }
126
127 $sTemporaryFile = CONST_BasePath.'/data/osmosischange.osc';
128 $bHaveDiff = false;
129 $bUseOSMApi = isset($aResult['import-from-main-api']) && $aResult['import-from-main-api'];
130 $sContentURL = '';
131 if (isset($aResult['import-node']) && $aResult['import-node']) {
132     if ($bUseOSMApi) {
133         $sContentURL = 'http://www.openstreetmap.org/api/0.6/node/'.$aResult['import-node'];
134     } else {
135         $sContentURL = 'http://overpass-api.de/api/interpreter?data=node('.$aResult['import-node'].');out%20meta;';
136     }
137 }
138
139 if (isset($aResult['import-way']) && $aResult['import-way']) {
140     if ($bUseOSMApi) {
141         $sContentURL = 'http://www.openstreetmap.org/api/0.6/way/'.$aResult['import-way'].'/full';
142     } else {
143         $sContentURL = 'http://overpass-api.de/api/interpreter?data=(way('.$aResult['import-way'].');node(w););out%20meta;';
144     }
145 }
146
147 if (isset($aResult['import-relation']) && $aResult['import-relation']) {
148     if ($bUseOSMApi) {
149         $sContentURLsModifyXMLstr = 'http://www.openstreetmap.org/api/0.6/relation/'.$aResult['import-relation'].'/full';
150     } else {
151         $sContentURL = 'http://overpass-api.de/api/interpreter?data=((rel('.$aResult['import-relation'].');way(r);node(w));node(r));out%20meta;';
152     }
153 }
154
155 if ($sContentURL) {
156     file_put_contents($sTemporaryFile, file_get_contents($sContentURL));
157     $bHaveDiff = true;
158 }
159
160 if ($bHaveDiff) {
161     // import generated change file
162     $sCMD = $sOsm2pgsqlCmd.' '.$sTemporaryFile;
163     echo $sCMD."\n";
164     exec($sCMD, $sJunk, $iErrorLevel);
165     if ($iErrorLevel) {
166         fail("osm2pgsql exited with error level $iErrorLevel\n");
167     }
168 }
169
170 if ($aResult['deduplicate']) {
171     $oDB =& getDB();
172
173     if (getPostgresVersion($oDB) < 9.3) {
174         fail('ERROR: deduplicate is only currently supported in postgresql 9.3');
175     }
176
177     $sSQL = 'select partition from country_name order by country_code';
178     $aPartitions = chksql($oDB->getCol($sSQL));
179     $aPartitions[] = 0;
180
181     // we don't care about empty search_name_* partitions, they can't contain mentions of duplicates
182     foreach ($aPartitions as $i => $sPartition) {
183         $sSQL = 'select count(*) from search_name_'.$sPartition;
184         $nEntries = chksql($oDB->getOne($sSQL));
185         if ($nEntries == 0) {
186             unset($aPartitions[$i]);
187         }
188     }
189
190     $sSQL = "select word_token,count(*) from word where substr(word_token, 1, 1) = ' '";
191     $sSQL .= ' and class is null and type is null and country_code is null';
192     $sSQL .= ' group by word_token having count(*) > 1 order by word_token';
193     $aDuplicateTokens = chksql($oDB->getAll($sSQL));
194     foreach ($aDuplicateTokens as $aToken) {
195         if (trim($aToken['word_token']) == '' || trim($aToken['word_token']) == '-') continue;
196         echo 'Deduping '.$aToken['word_token']."\n";
197         $sSQL = 'select word_id,';
198         $sSQL .= ' (select count(*) from search_name where nameaddress_vector @> ARRAY[word_id]) as num';
199         $sSQL .= " from word where word_token = '".$aToken['word_token'];
200         $sSQL .= "' and class is null and type is null and country_code is null order by num desc";
201         $aTokenSet = chksql($oDB->getAll($sSQL));
202
203         $aKeep = array_shift($aTokenSet);
204         $iKeepID = $aKeep['word_id'];
205
206         foreach ($aTokenSet as $aRemove) {
207             $sSQL = 'update search_name set';
208             $sSQL .= ' name_vector = array_replace(name_vector,'.$aRemove['word_id'].','.$iKeepID.'),';
209             $sSQL .= ' nameaddress_vector = array_replace(nameaddress_vector,'.$aRemove['word_id'].','.$iKeepID.')';
210             $sSQL .= ' where name_vector @> ARRAY['.$aRemove['word_id'].']';
211             chksql($oDB->query($sSQL));
212
213             $sSQL = 'update search_name set';
214             $sSQL .= ' nameaddress_vector = array_replace(nameaddress_vector,'.$aRemove['word_id'].','.$iKeepID.')';
215             $sSQL .= ' where nameaddress_vector @> ARRAY['.$aRemove['word_id'].']';
216             chksql($oDB->query($sSQL));
217
218             $sSQL = 'update location_area_country set';
219             $sSQL .= ' keywords = array_replace(keywords,'.$aRemove['word_id'].','.$iKeepID.')';
220             $sSQL .= ' where keywords @> ARRAY['.$aRemove['word_id'].']';
221             chksql($oDB->query($sSQL));
222
223             foreach ($aPartitions as $sPartition) {
224                 $sSQL = 'update search_name_'.$sPartition.' set';
225                 $sSQL .= ' name_vector = array_replace(name_vector,'.$aRemove['word_id'].','.$iKeepID.')';
226                 $sSQL .= ' where name_vector @> ARRAY['.$aRemove['word_id'].']';
227                 chksql($oDB->query($sSQL));
228
229                 $sSQL = 'update location_area_country set';
230                 $sSQL .= ' keywords = array_replace(keywords,'.$aRemove['word_id'].','.$iKeepID.')';
231                 $sSQL .= ' where keywords @> ARRAY['.$aRemove['word_id'].']';
232                 chksql($oDB->query($sSQL));
233             }
234
235             $sSQL = 'delete from word where word_id = '.$aRemove['word_id'];
236             chksql($oDB->query($sSQL));
237         }
238     }
239 }
240
241 if ($aResult['index']) {
242     passthru(CONST_InstallPath.'/nominatim/nominatim -i -d '.$aDSNInfo['database'].' -P '.$aDSNInfo['port'].' -t '.$aResult['index-instances'].' -r '.$aResult['index-rank']);
243 }
244
245 if ($aResult['import-osmosis'] || $aResult['import-osmosis-all']) {
246     //
247     if (strpos(CONST_Replication_Url, 'download.geofabrik.de') !== false && CONST_Replication_Update_Interval < 86400) {
248         fail("Error: Update interval too low for download.geofabrik.de.  Please check install documentation (http://wiki.openstreetmap.org/wiki/Nominatim/Installation#Updates)\n");
249     }
250
251     $sImportFile = CONST_InstallPath.'/osmosischange.osc';
252     $sCMDDownload = CONST_Pyosmium_Binary.' --server '.CONST_Replication_Url.' -o '.$sImportFile.' -s '.CONST_Replication_Max_Diff_size;
253     $sCMDImport = $sOsm2pgsqlCmd.' '.$sImportFile;
254     $sCMDIndex = CONST_InstallPath.'/nominatim/nominatim -i -d '.$aDSNInfo['database'].' -P '.$aDSNInfo['port'].' -t '.$aResult['index-instances'];
255
256     while (true) {
257         $fStartTime = time();
258         $aLastState = chksql($oDB->getRow('SELECT *, EXTRACT (EPOCH FROM lastimportdate) as unix_ts FROM import_status'));
259
260         if (!$aLastState['sequence_id']) {
261             echo "Updates not set up. Please run ./utils/update.php --init-updates.\n";
262             exit(1);
263         }
264
265         echo 'Currently at sequence '.$aLastState['sequence_id'].' ('.$aLastState['lastimportdate'].') - '.$aLastState['indexed']." indexed\n";
266
267         $sBatchEnd = $aLastState['lastimportdate'];
268         $iEndSequence = $aLastState['sequence_id'];
269
270         if ($aLastState['indexed'] == 't') {
271             // Sleep if the update interval has not yet been reached.
272             $fNextUpdate = $aLastState['unix_ts'] + CONST_Replication_Update_Interval;
273             if ($fNextUpdate > $fStartTime) {
274                 $iSleepTime = $fNextUpdate - $fStartTime;
275                 echo "Waiting for next update for $iSleepTime sec.";
276                 sleep($iSleepTime);
277             }
278
279             // Download the next batch of changes.
280             do {
281                 $fCMDStartTime = time();
282                 $iNextSeq = (int) $aLastState['sequence_id'];
283                 unset($aOutput);
284                 echo "$sCMDDownload -I $iNextSeq\n";
285                 unlink($sImportFile);
286                 exec($sCMDDownload.' -I '.$iNextSeq, $aOutput, $iResult);
287
288                 if ($iResult == 3) {
289                     echo 'No new updates. Sleeping for '.CONST_Replication_Recheck_Interval." sec.\n";
290                     sleep(CONST_Replication_Recheck_Interval);
291                 } elseif ($iResult != 0) {
292                     echo 'ERROR: updates failed.';
293                     exit($iResult);
294                 } else {
295                     $iEndSequence = (int)$aOutput[0];
296                 }
297             } while ($iResult);
298
299             // get the newest object from the diff file
300             $sBatchEnd = 0;
301             $iRet = 0;
302             exec(CONST_BasePath.'/utils/osm_file_date.py '.$sImportFile, $sBatchEnd, $iRet);
303             if ($iRet == 5) {
304                 echo "Diff file is empty. skipping import.\n";
305                 if (!$aResult['import-osmosis-all']) {
306                     exit(0);
307                 } else {
308                     continue;
309                 }
310             }
311             if ($iRet != 0) {
312                 fail('Error getting date from diff file.');
313             }
314             $sBatchEnd = $sBatchEnd[0];
315
316             // Import the file
317             $fCMDStartTime = time();
318             echo $sCMDImport."\n";
319             unset($sJunk);
320             exec($sCMDImport, $sJunk, $iErrorLevel);
321             if ($iErrorLevel) {
322                 echo "Error executing osm2pgsql: $iErrorLevel\n";
323                 exit($iErrorLevel);
324             }
325
326             // write the update logs
327             $iFileSize = filesize($sImportFile);
328             $sSQL = 'INSERT INTO import_osmosis_log';
329             $sSQL .= '(batchend, batchseq, batchsize, starttime, endtime, event)';
330             $sSQL .= " values ('$sBatchEnd',$iEndSequence,$iFileSize,'";
331             $sSQL .= date('Y-m-d H:i:s', $fCMDStartTime)."','";
332             $sSQL .= date('Y-m-d H:i:s')."','import')";
333             var_Dump($sSQL);
334             chksql($oDB->query($sSQL));
335
336             // update the status
337             $sSQL = "UPDATE import_status SET lastimportdate = '$sBatchEnd', indexed=false, sequence_id = $iEndSequence";
338             var_Dump($sSQL);
339             chksql($oDB->query($sSQL));
340             echo date('Y-m-d H:i:s')." Completed download step for $sBatchEnd in ".round((time()-$fCMDStartTime)/60, 2)." minutes\n";
341         }
342
343         // Index file
344         if (!$aResult['no-index']) {
345             $sThisIndexCmd = $sCMDIndex;
346             $fCMDStartTime = time();
347
348             echo "$sThisIndexCmd\n";
349             exec($sThisIndexCmd, $sJunk, $iErrorLevel);
350             if ($iErrorLevel) {
351                 echo "Error: $iErrorLevel\n";
352                 exit($iErrorLevel);
353             }
354
355             $sSQL = 'INSERT INTO import_osmosis_log';
356             $sSQL .= '(batchend, batchseq, batchsize, starttime, endtime, event)';
357             $sSQL .= " values ('$sBatchEnd',$iEndSequence,$iFileSize,'";
358             $sSQL .= date('Y-m-d H:i:s', $fCMDStartTime)."','";
359             $sSQL .= date('Y-m-d H:i:s')."','index')";
360             var_Dump($sSQL);
361             $oDB->query($sSQL);
362             echo date('Y-m-d H:i:s')." Completed index step for $sBatchEnd in ".round((time()-$fCMDStartTime)/60, 2)." minutes\n";
363
364             $sSQL = 'update import_status set indexed = true';
365             $oDB->query($sSQL);
366         }
367
368         $fDuration = time() - $fStartTime;
369         echo date('Y-m-d H:i:s')." Completed all for $sBatchEnd in ".round($fDuration/60, 2)." minutes\n";
370         if (!$aResult['import-osmosis-all']) exit(0);
371     }
372 }