]> git.openstreetmap.org Git - nominatim.git/blob - utils/update.php
27c538c17facda16db1aaf6860df22b33a110602
[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     //
160     if (getPostgresVersion() < 9.3) {
161         fail("ERROR: deduplicate is only currently supported in postgresql 9.3");
162     }
163
164     $oDB =& getDB();
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) = ' ' and class is null and type is null and country_code is null group by word_token having count(*) > 1 order by word_token";
170     $aDuplicateTokens = chksql($oDB->getAll($sSQL));
171     foreach ($aDuplicateTokens as $aToken) {
172         if (trim($aToken['word_token']) == '' || trim($aToken['word_token']) == '-') continue;
173         echo "Deduping ".$aToken['word_token']."\n";
174         $sSQL = "select word_id,(select count(*) from search_name where nameaddress_vector @> ARRAY[word_id]) as num from word where word_token = '".$aToken['word_token']."' and class is null and type is null and country_code is null order by num desc";
175         $aTokenSet = chksql($oDB->getAll($sSQL));
176
177         $aKeep = array_shift($aTokenSet);
178         $iKeepID = $aKeep['word_id'];
179
180         foreach ($aTokenSet as $aRemove) {
181             $sSQL = "update search_name set";
182             $sSQL .= " name_vector = array_replace(name_vector,".$aRemove['word_id'].",".$iKeepID."),";
183             $sSQL .= " nameaddress_vector = array_replace(nameaddress_vector,".$aRemove['word_id'].",".$iKeepID.")";
184             $sSQL .= " where name_vector @> ARRAY[".$aRemove['word_id']."]";
185             chksql($oDB->query($sSQL));
186
187             $sSQL = "update search_name set";
188             $sSQL .= " nameaddress_vector = array_replace(nameaddress_vector,".$aRemove['word_id'].",".$iKeepID.")";
189             $sSQL .= " where nameaddress_vector @> ARRAY[".$aRemove['word_id']."]";
190             chksql($oDB->query($sSQL));
191
192             $sSQL = "update location_area_country set";
193             $sSQL .= " keywords = array_replace(keywords,".$aRemove['word_id'].",".$iKeepID.")";
194             $sSQL .= " where keywords @> ARRAY[".$aRemove['word_id']."]";
195             chksql($oDB->query($sSQL));
196
197             foreach ($aPartitions as $sPartition) {
198                 $sSQL = "update search_name_".$sPartition." set";
199                 $sSQL .= " name_vector = array_replace(name_vector,".$aRemove['word_id'].",".$iKeepID.")";
200                 $sSQL .= " where name_vector @> ARRAY[".$aRemove['word_id']."]";
201                 chksql($oDB->query($sSQL));
202
203                 $sSQL = "update location_area_country set";
204                 $sSQL .= " keywords = array_replace(keywords,".$aRemove['word_id'].",".$iKeepID.")";
205                 $sSQL .= " where keywords @> ARRAY[".$aRemove['word_id']."]";
206                 chksql($oDB->query($sSQL));
207             }
208
209             $sSQL = "delete from word where word_id = ".$aRemove['word_id'];
210             chksql($oDB->query($sSQL));
211         }
212     }
213 }
214
215 if ($aResult['index']) {
216     passthru(CONST_InstallPath.'/nominatim/nominatim -i -d '.$aDSNInfo['database'].' -P '.$aDSNInfo['port'].' -t '.$aResult['index-instances'].' -r '.$aResult['index-rank']);
217 }
218
219 if ($aResult['import-osmosis'] || $aResult['import-osmosis-all']) {
220     //
221     if (strpos(CONST_Replication_Url, 'download.geofabrik.de') !== false && CONST_Replication_Update_Interval < 86400) {
222         fail("Error: Update interval too low for download.geofabrik.de.  Please check install documentation (http://wiki.openstreetmap.org/wiki/Nominatim/Installation#Updates)\n");
223     }
224
225     $sImportFile = CONST_BasePath.'/data/osmosischange.osc';
226     $sOsmosisConfigDirectory = CONST_InstallPath.'/settings';
227     $sCMDDownload = CONST_Osmosis_Binary.' --read-replication-interval workingDirectory='.$sOsmosisConfigDirectory.' --simplify-change --write-xml-change '.$sImportFile;
228     $sCMDCheckReplicationLag = CONST_Osmosis_Binary.' -q --read-replication-lag workingDirectory='.$sOsmosisConfigDirectory;
229     $sCMDImport = $sOsm2pgsqlCmd.' '.$sImportFile;
230     $sCMDIndex = CONST_InstallPath.'/nominatim/nominatim -i -d '.$aDSNInfo['database'].' -P '.$aDSNInfo['port'].' -t '.$aResult['index-instances'];
231
232     while (true) {
233         $fStartTime = time();
234         $iFileSize = 1001;
235
236         if (!file_exists($sImportFile)) {
237             // First check if there are new updates published (except for minutelies - there's always new diffs to process)
238             if (CONST_Replication_Update_Interval > 60) {
239                 unset($aReplicationLag);
240                 exec($sCMDCheckReplicationLag, $aReplicationLag, $iErrorLevel); 
241                 while ($iErrorLevel > 0 || $aReplicationLag[0] < 1) {
242                     if ($iErrorLevel) {
243                         echo "Error: $iErrorLevel. ";
244                         echo "Re-trying: ".$sCMDCheckReplicationLag." in ".CONST_Replication_Recheck_Interval." secs\n";
245                     } else {
246                         echo ".";
247                     }
248                     sleep(CONST_Replication_Recheck_Interval);
249                     unset($aReplicationLag);
250                     exec($sCMDCheckReplicationLag, $aReplicationLag, $iErrorLevel); 
251                 }
252                 // There are new replication files - use osmosis to download the file
253                 echo "\n".date('Y-m-d H:i:s')." Replication Delay is ".$aReplicationLag[0]."\n";
254             }
255             $fStartTime = time();
256             $fCMDStartTime = time();
257             echo $sCMDDownload."\n";
258             exec($sCMDDownload, $sJunk, $iErrorLevel);
259             while ($iErrorLevel > 0) {
260                 echo "Error: $iErrorLevel\n";
261                 sleep(60);
262                 echo 'Re-trying: '.$sCMDDownload."\n";
263                 exec($sCMDDownload, $sJunk, $iErrorLevel);
264             }
265             $iFileSize = filesize($sImportFile);
266             $sBatchEnd = getosmosistimestamp($sOsmosisConfigDirectory);
267             $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')";
268             var_Dump($sSQL);
269             $oDB->query($sSQL);
270             echo date('Y-m-d H:i:s')." Completed osmosis step for $sBatchEnd in ".round((time()-$fCMDStartTime)/60, 2)." minutes\n";
271         }
272
273         $iFileSize = filesize($sImportFile);
274         $sBatchEnd = getosmosistimestamp($sOsmosisConfigDirectory);
275
276         // Import the file
277         $fCMDStartTime = time();
278         echo $sCMDImport."\n";
279         exec($sCMDImport, $sJunk, $iErrorLevel);
280         if ($iErrorLevel) {
281             echo "Error: $iErrorLevel\n";
282             exit($iErrorLevel);
283         }
284         $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')";
285         var_Dump($sSQL);
286         $oDB->query($sSQL);
287         echo date('Y-m-d H:i:s')." Completed osm2pgsql step for $sBatchEnd in ".round((time()-$fCMDStartTime)/60, 2)." minutes\n";
288
289         // Archive for debug?
290         unlink($sImportFile);
291
292         $sBatchEnd = getosmosistimestamp($sOsmosisConfigDirectory);
293
294         // Index file
295         $sThisIndexCmd = $sCMDIndex;
296         $fCMDStartTime = time();
297
298         if (!$aResult['no-index']) {
299             echo "$sThisIndexCmd\n";
300             exec($sThisIndexCmd, $sJunk, $iErrorLevel);
301             if ($iErrorLevel) {
302                 echo "Error: $iErrorLevel\n";
303                 exit($iErrorLevel);
304             }
305         }
306
307         $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')";
308         var_Dump($sSQL);
309         $oDB->query($sSQL);
310         echo date('Y-m-d H:i:s')." Completed index step for $sBatchEnd in ".round((time()-$fCMDStartTime)/60, 2)." minutes\n";
311
312         $sSQL = "update import_status set lastimportdate = '$sBatchEnd'";
313         $oDB->query($sSQL);
314
315         $fDuration = time() - $fStartTime;
316         echo date('Y-m-d H:i:s')." Completed all for $sBatchEnd in ".round($fDuration/60, 2)." minutes\n";
317         if (!$aResult['import-osmosis-all']) exit(0);
318
319         if (CONST_Replication_Update_Interval > 60) {
320             $iSleep = max(0, (strtotime($sBatchEnd)+CONST_Replication_Update_Interval-time()));
321         } else {
322             $iSleep = max(0, CONST_Replication_Update_Interval-$fDuration);
323         }
324         echo date('Y-m-d H:i:s')." Sleeping $iSleep seconds\n";
325         sleep($iSleep);
326     }
327 }
328
329 function getosmosistimestamp($sOsmosisConfigDirectory)
330 {
331     $sStateFile = file_get_contents($sOsmosisConfigDirectory.'/state.txt');
332     preg_match('#timestamp=(.+)#', $sStateFile, $aResult);
333     return str_replace('\:', ':', $aResult[1]);
334 }