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