]> git.openstreetmap.org Git - nominatim.git/blob - utils/update.php
update: getPostgresVersion requires a database connection
[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('import-osmosis', '', 0, 1, 0, 0, 'bool', 'Import using osmosis'),
16    array('import-osmosis-all', '', 0, 1, 0, 0, 'bool', 'Import using osmosis forever'),
17    array('no-npi', '', 0, 1, 0, 0, 'bool', '(obsolate)'),
18    array('no-index', '', 0, 1, 0, 0, 'bool', 'Do not index the new data'),
19
20    array('import-all', '', 0, 1, 0, 0, 'bool', 'Import all available files'),
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   );
37 getCmdOpt($_SERVER['argv'], $aCMDOptions, $aResult, true, true);
38
39 if (!isset($aResult['index-instances'])) $aResult['index-instances'] = 1;
40 if (!isset($aResult['index-rank'])) $aResult['index-rank'] = 0;
41
42 date_default_timezone_set('Etc/UTC');
43
44 $oDB =& getDB();
45
46 $aDSNInfo = DB::parseDSN(CONST_Database_DSN);
47 if (!isset($aDSNInfo['port']) || !$aDSNInfo['port']) $aDSNInfo['port'] = 5432;
48
49 // cache memory to be used by osm2pgsql, should not be more than the available memory
50 $iCacheMemory = (isset($aResult['osm2pgsql-cache'])?$aResult['osm2pgsql-cache']:2000);
51 if ($iCacheMemory + 500 > getTotalMemoryMB()) {
52     $iCacheMemory = getCacheMemoryMB();
53     echo "WARNING: resetting cache memory to $iCacheMemory\n";
54 }
55 $sOsm2pgsqlCmd = CONST_Osm2pgsql_Binary.' -klas --number-processes 1 -C '.$iCacheMemory.' -O gazetteer -d '.$aDSNInfo['database'].' -P '.$aDSNInfo['port'];
56 if (!is_null(CONST_Osm2pgsql_Flatnode_File)) {
57     $sOsm2pgsqlCmd .= ' --flat-nodes '.CONST_Osm2pgsql_Flatnode_File;
58 }
59
60
61 if (isset($aResult['import-diff'])) {
62     // import diff directly (e.g. from osmosis --rri)
63     $sNextFile = $aResult['import-diff'];
64     if (!file_exists($sNextFile)) {
65         fail("Cannot open $sNextFile\n");
66     }
67
68     // Import the file
69     $sCMD = $sOsm2pgsqlCmd.' '.$sNextFile;
70     echo $sCMD."\n";
71     exec($sCMD, $sJunk, $iErrorLevel);
72
73     if ($iErrorLevel) {
74         fail("Error from osm2pgsql, $iErrorLevel\n");
75     }
76
77     // Don't update the import status - we don't know what this file contains
78 }
79
80 $sTemporaryFile = CONST_BasePath.'/data/osmosischange.osc';
81 $bHaveDiff = false;
82 if (isset($aResult['import-file']) && $aResult['import-file']) {
83     $bHaveDiff = true;
84     $sCMD = CONST_Osmosis_Binary.' --read-xml \''.$aResult['import-file'].'\' --read-empty --derive-change --write-xml-change '.$sTemporaryFile;
85     echo $sCMD."\n";
86     exec($sCMD, $sJunk, $iErrorLevel);
87     if ($iErrorLevel) {
88         fail("Error converting osm to osc, osmosis returned: $iErrorLevel\n");
89     }
90 }
91
92 $bUseOSMApi = isset($aResult['import-from-main-api']) && $aResult['import-from-main-api'];
93 $sContentURL = '';
94 if (isset($aResult['import-node']) && $aResult['import-node']) {
95     if ($bUseOSMApi) {
96         $sContentURL = 'http://www.openstreetmap.org/api/0.6/node/'.$aResult['import-node'];
97     } else {
98         $sContentURL = 'http://overpass-api.de/api/interpreter?data=node('.$aResult['import-node'].');out%20meta;';
99     }
100 }
101
102 if (isset($aResult['import-way']) && $aResult['import-way']) {
103     if ($bUseOSMApi) {
104         $sContentURL = 'http://www.openstreetmap.org/api/0.6/way/'.$aResult['import-way'].'/full';
105     } else {
106         $sContentURL = 'http://overpass-api.de/api/interpreter?data=(way('.$aResult['import-way'].');node(w););out%20meta;';
107     }
108 }
109
110 if (isset($aResult['import-relation']) && $aResult['import-relation']) {
111     if ($bUseOSMApi) {
112         $sContentURLsModifyXMLstr = 'http://www.openstreetmap.org/api/0.6/relation/'.$aResult['import-relation'].'/full';
113     } else {
114         $sContentURL = 'http://overpass-api.de/api/interpreter?data=((rel('.$aResult['import-relation'].');way(r);node(w));node(r));out%20meta;';
115     }
116 }
117
118 if ($sContentURL) {
119     $sModifyXMLstr = file_get_contents($sContentURL);
120     $bHaveDiff = true;
121
122     $aSpec = array(
123               0 => array("pipe", "r"),  // stdin
124               1 => array("pipe", "w"),  // stdout
125               2 => array("pipe", "w") // stderr
126              );
127     $sCMD = CONST_Osmosis_Binary.' --read-xml - --read-empty --derive-change --write-xml-change '.$sTemporaryFile;
128     echo $sCMD."\n";
129     $hProc = proc_open($sCMD, $aSpec, $aPipes);
130     if (!is_resource($hProc)) {
131         fail("Error converting osm to osc, osmosis failed\n");
132     }
133     fwrite($aPipes[0], $sModifyXMLstr);
134     fclose($aPipes[0]);
135     $sOut = stream_get_contents($aPipes[1]);
136     if ($aResult['verbose']) echo $sOut;
137     fclose($aPipes[1]);
138     $sErrors = stream_get_contents($aPipes[2]);
139     if ($aResult['verbose']) echo $sErrors;
140     fclose($aPipes[2]);
141     if ($iError = proc_close($hProc)) {
142         echo $sOut;
143         echo $sErrors;
144         fail("Error converting osm to osc, osmosis returned: $iError\n");
145     }
146 }
147
148 if ($bHaveDiff) {
149     // import generated change file
150     $sCMD = $sOsm2pgsqlCmd.' '.$sTemporaryFile;
151     echo $sCMD."\n";
152     exec($sCMD, $sJunk, $iErrorLevel);
153     if ($iErrorLevel) {
154         fail("osm2pgsql exited with error level $iErrorLevel\n");
155     }
156 }
157
158 if ($aResult['deduplicate']) {
159     $oDB =& getDB();
160
161     if (getPostgresVersion($oDB) < 9.3) {
162         fail("ERROR: deduplicate is only currently supported in postgresql 9.3");
163     }
164
165     $sSQL = 'select partition from country_name order by country_code';
166     $aPartitions = chksql($oDB->getCol($sSQL));
167     $aPartitions[] = 0;
168
169     $sSQL = "select word_token,count(*) from word where substr(word_token, 1, 1) = ' '";
170     $sSQL .= " and class is null and type is null and country_code is null";
171     $sSQL .= " group by word_token having count(*) > 1 order by word_token";
172     $aDuplicateTokens = chksql($oDB->getAll($sSQL));
173     foreach ($aDuplicateTokens as $aToken) {
174         if (trim($aToken['word_token']) == '' || trim($aToken['word_token']) == '-') continue;
175         echo "Deduping ".$aToken['word_token']."\n";
176         $sSQL = "select word_id,";
177         $sSQL .= " (select count(*) from search_name where nameaddress_vector @> ARRAY[word_id]) as num";
178         $sSQL .= " from word where word_token = '".$aToken['word_token'];
179         $sSQL .= "' and class is null and type is null and country_code is null order by num desc";
180         $aTokenSet = chksql($oDB->getAll($sSQL));
181
182         $aKeep = array_shift($aTokenSet);
183         $iKeepID = $aKeep['word_id'];
184
185         foreach ($aTokenSet as $aRemove) {
186             $sSQL = "update search_name set";
187             $sSQL .= " name_vector = array_replace(name_vector,".$aRemove['word_id'].",".$iKeepID."),";
188             $sSQL .= " nameaddress_vector = array_replace(nameaddress_vector,".$aRemove['word_id'].",".$iKeepID.")";
189             $sSQL .= " where name_vector @> ARRAY[".$aRemove['word_id']."]";
190             chksql($oDB->query($sSQL));
191
192             $sSQL = "update search_name set";
193             $sSQL .= " nameaddress_vector = array_replace(nameaddress_vector,".$aRemove['word_id'].",".$iKeepID.")";
194             $sSQL .= " where nameaddress_vector @> ARRAY[".$aRemove['word_id']."]";
195             chksql($oDB->query($sSQL));
196
197             $sSQL = "update location_area_country set";
198             $sSQL .= " keywords = array_replace(keywords,".$aRemove['word_id'].",".$iKeepID.")";
199             $sSQL .= " where keywords @> ARRAY[".$aRemove['word_id']."]";
200             chksql($oDB->query($sSQL));
201
202             foreach ($aPartitions as $sPartition) {
203                 $sSQL = "update search_name_".$sPartition." set";
204                 $sSQL .= " name_vector = array_replace(name_vector,".$aRemove['word_id'].",".$iKeepID.")";
205                 $sSQL .= " where name_vector @> ARRAY[".$aRemove['word_id']."]";
206                 chksql($oDB->query($sSQL));
207
208                 $sSQL = "update location_area_country set";
209                 $sSQL .= " keywords = array_replace(keywords,".$aRemove['word_id'].",".$iKeepID.")";
210                 $sSQL .= " where keywords @> ARRAY[".$aRemove['word_id']."]";
211                 chksql($oDB->query($sSQL));
212             }
213
214             $sSQL = "delete from word where word_id = ".$aRemove['word_id'];
215             chksql($oDB->query($sSQL));
216         }
217     }
218 }
219
220 if ($aResult['index']) {
221     passthru(CONST_InstallPath.'/nominatim/nominatim -i -d '.$aDSNInfo['database'].' -P '.$aDSNInfo['port'].' -t '.$aResult['index-instances'].' -r '.$aResult['index-rank']);
222 }
223
224 if ($aResult['import-osmosis'] || $aResult['import-osmosis-all']) {
225     //
226     if (strpos(CONST_Replication_Url, 'download.geofabrik.de') !== false && CONST_Replication_Update_Interval < 86400) {
227         fail("Error: Update interval too low for download.geofabrik.de.  Please check install documentation (http://wiki.openstreetmap.org/wiki/Nominatim/Installation#Updates)\n");
228     }
229
230     $sImportFile = CONST_BasePath.'/data/osmosischange.osc';
231     $sOsmosisConfigDirectory = CONST_InstallPath.'/settings';
232     $sCMDDownload = CONST_Osmosis_Binary.' --read-replication-interval workingDirectory='.$sOsmosisConfigDirectory.' --simplify-change --write-xml-change '.$sImportFile;
233     $sCMDCheckReplicationLag = CONST_Osmosis_Binary.' -q --read-replication-lag workingDirectory='.$sOsmosisConfigDirectory;
234     $sCMDImport = $sOsm2pgsqlCmd.' '.$sImportFile;
235     $sCMDIndex = CONST_InstallPath.'/nominatim/nominatim -i -d '.$aDSNInfo['database'].' -P '.$aDSNInfo['port'].' -t '.$aResult['index-instances'];
236
237     while (true) {
238         $fStartTime = time();
239         $iFileSize = 1001;
240
241         if (!file_exists($sImportFile)) {
242             // First check if there are new updates published (except for minutelies - there's always new diffs to process)
243             if (CONST_Replication_Update_Interval > 60) {
244                 unset($aReplicationLag);
245                 exec($sCMDCheckReplicationLag, $aReplicationLag, $iErrorLevel);
246                 while ($iErrorLevel > 0 || $aReplicationLag[0] < 1) {
247                     if ($iErrorLevel) {
248                         echo "Error: $iErrorLevel. ";
249                         echo "Re-trying: ".$sCMDCheckReplicationLag." in ".CONST_Replication_Recheck_Interval." secs\n";
250                     } else {
251                         echo ".";
252                     }
253                     sleep(CONST_Replication_Recheck_Interval);
254                     unset($aReplicationLag);
255                     exec($sCMDCheckReplicationLag, $aReplicationLag, $iErrorLevel);
256                 }
257                 // There are new replication files - use osmosis to download the file
258                 echo "\n".date('Y-m-d H:i:s')." Replication Delay is ".$aReplicationLag[0]."\n";
259             }
260             $fStartTime = time();
261             $fCMDStartTime = time();
262             echo $sCMDDownload."\n";
263             exec($sCMDDownload, $sJunk, $iErrorLevel);
264             while ($iErrorLevel > 0) {
265                 echo "Error: $iErrorLevel\n";
266                 sleep(60);
267                 echo 'Re-trying: '.$sCMDDownload."\n";
268                 exec($sCMDDownload, $sJunk, $iErrorLevel);
269             }
270             $iFileSize = filesize($sImportFile);
271             $sBatchEnd = getosmosistimestamp($sOsmosisConfigDirectory);
272             $sSQL = "INSERT INTO import_osmosis_log values ('$sBatchEnd',$iFileSize,'".date('Y-m-d H:i:s', $fCMDStartTime)."','".date('Y-m-d H:i:s')."','osmosis')";
273             var_Dump($sSQL);
274             $oDB->query($sSQL);
275             echo date('Y-m-d H:i:s')." Completed osmosis step for $sBatchEnd in ".round((time()-$fCMDStartTime)/60, 2)." minutes\n";
276         }
277
278         $iFileSize = filesize($sImportFile);
279         $sBatchEnd = getosmosistimestamp($sOsmosisConfigDirectory);
280
281         // Import the file
282         $fCMDStartTime = time();
283         echo $sCMDImport."\n";
284         exec($sCMDImport, $sJunk, $iErrorLevel);
285         if ($iErrorLevel) {
286             echo "Error: $iErrorLevel\n";
287             exit($iErrorLevel);
288         }
289         $sSQL = "INSERT INTO import_osmosis_log values ('$sBatchEnd',$iFileSize,'".date('Y-m-d H:i:s', $fCMDStartTime)."','".date('Y-m-d H:i:s')."','osm2pgsql')";
290         var_Dump($sSQL);
291         $oDB->query($sSQL);
292         echo date('Y-m-d H:i:s')." Completed osm2pgsql step for $sBatchEnd in ".round((time()-$fCMDStartTime)/60, 2)." minutes\n";
293
294         // Archive for debug?
295         unlink($sImportFile);
296
297         $sBatchEnd = getosmosistimestamp($sOsmosisConfigDirectory);
298
299         // Index file
300         $sThisIndexCmd = $sCMDIndex;
301         $fCMDStartTime = time();
302
303         if (!$aResult['no-index']) {
304             echo "$sThisIndexCmd\n";
305             exec($sThisIndexCmd, $sJunk, $iErrorLevel);
306             if ($iErrorLevel) {
307                 echo "Error: $iErrorLevel\n";
308                 exit($iErrorLevel);
309             }
310         }
311
312         $sSQL = "INSERT INTO import_osmosis_log values ('$sBatchEnd',$iFileSize,'".date('Y-m-d H:i:s', $fCMDStartTime)."','".date('Y-m-d H:i:s')."','index')";
313         var_Dump($sSQL);
314         $oDB->query($sSQL);
315         echo date('Y-m-d H:i:s')." Completed index step for $sBatchEnd in ".round((time()-$fCMDStartTime)/60, 2)." minutes\n";
316
317         $sSQL = "update import_status set lastimportdate = '$sBatchEnd'";
318         $oDB->query($sSQL);
319
320         $fDuration = time() - $fStartTime;
321         echo date('Y-m-d H:i:s')." Completed all for $sBatchEnd in ".round($fDuration/60, 2)." minutes\n";
322         if (!$aResult['import-osmosis-all']) exit(0);
323
324         if (CONST_Replication_Update_Interval > 60) {
325             $iSleep = max(0, (strtotime($sBatchEnd)+CONST_Replication_Update_Interval-time()));
326         } else {
327             $iSleep = max(0, CONST_Replication_Update_Interval-$fDuration);
328         }
329         echo date('Y-m-d H:i:s')." Sleeping $iSleep seconds\n";
330         sleep($iSleep);
331     }
332 }
333
334
335 function getosmosistimestamp($sOsmosisConfigDirectory)
336 {
337     $sStateFile = file_get_contents($sOsmosisConfigDirectory.'/state.txt');
338     preg_match('#timestamp=(.+)#', $sStateFile, $aResult);
339     return str_replace('\:', ':', $aResult[1]);
340 }