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