]> git.openstreetmap.org Git - nominatim.git/blob - utils/update.php
Merge pull request #868 from JonathanMontane/feat/export
[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('recompute-word-counts', '', 0, 1, 0, 0, 'bool', 'Compute frequency of full-word search terms'),
37    array('no-npi', '', 0, 1, 0, 0, 'bool', '(obsolete)'),
38   );
39 getCmdOpt($_SERVER['argv'], $aCMDOptions, $aResult, true, true);
40
41 if (!isset($aResult['index-instances'])) $aResult['index-instances'] = 1;
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['recompute-word-counts']) {
242     info('Recompute frequency of full-word search terms');
243     $sTemplate = file_get_contents(CONST_BasePath.'/sql/words_from_search_name.sql');
244     runSQLScript($sTemplate, true, true);
245 }
246
247 if ($aResult['index']) {
248     passthru(CONST_InstallPath.'/nominatim/nominatim -i -d '.$aDSNInfo['database'].' -P '.$aDSNInfo['port'].' -t '.$aResult['index-instances'].' -r '.$aResult['index-rank']);
249 }
250
251 if ($aResult['import-osmosis'] || $aResult['import-osmosis-all']) {
252     //
253     if (strpos(CONST_Replication_Url, 'download.geofabrik.de') !== false && CONST_Replication_Update_Interval < 86400) {
254         fail("Error: Update interval too low for download.geofabrik.de.  Please check install documentation (http://wiki.openstreetmap.org/wiki/Nominatim/Installation#Updates)\n");
255     }
256
257     $sImportFile = CONST_InstallPath.'/osmosischange.osc';
258     $sCMDDownload = CONST_Pyosmium_Binary.' --server '.CONST_Replication_Url.' -o '.$sImportFile.' -s '.CONST_Replication_Max_Diff_size;
259     $sCMDImport = $sOsm2pgsqlCmd.' '.$sImportFile;
260     $sCMDIndex = CONST_InstallPath.'/nominatim/nominatim -i -d '.$aDSNInfo['database'].' -P '.$aDSNInfo['port'].' -t '.$aResult['index-instances'];
261
262     while (true) {
263         $fStartTime = time();
264         $aLastState = chksql($oDB->getRow('SELECT *, EXTRACT (EPOCH FROM lastimportdate) as unix_ts FROM import_status'));
265
266         if (!$aLastState['sequence_id']) {
267             echo "Updates not set up. Please run ./utils/update.php --init-updates.\n";
268             exit(1);
269         }
270
271         echo 'Currently at sequence '.$aLastState['sequence_id'].' ('.$aLastState['lastimportdate'].') - '.$aLastState['indexed']." indexed\n";
272
273         $sBatchEnd = $aLastState['lastimportdate'];
274         $iEndSequence = $aLastState['sequence_id'];
275
276         if ($aLastState['indexed'] == 't') {
277             // Sleep if the update interval has not yet been reached.
278             $fNextUpdate = $aLastState['unix_ts'] + CONST_Replication_Update_Interval;
279             if ($fNextUpdate > $fStartTime) {
280                 $iSleepTime = $fNextUpdate - $fStartTime;
281                 echo "Waiting for next update for $iSleepTime sec.";
282                 sleep($iSleepTime);
283             }
284
285             // Download the next batch of changes.
286             do {
287                 $fCMDStartTime = time();
288                 $iNextSeq = (int) $aLastState['sequence_id'];
289                 unset($aOutput);
290                 echo "$sCMDDownload -I $iNextSeq\n";
291                 unlink($sImportFile);
292                 exec($sCMDDownload.' -I '.$iNextSeq, $aOutput, $iResult);
293
294                 if ($iResult == 3) {
295                     echo 'No new updates. Sleeping for '.CONST_Replication_Recheck_Interval." sec.\n";
296                     sleep(CONST_Replication_Recheck_Interval);
297                 } elseif ($iResult != 0) {
298                     echo 'ERROR: updates failed.';
299                     exit($iResult);
300                 } else {
301                     $iEndSequence = (int)$aOutput[0];
302                 }
303             } while ($iResult);
304
305             // get the newest object from the diff file
306             $sBatchEnd = 0;
307             $iRet = 0;
308             exec(CONST_BasePath.'/utils/osm_file_date.py '.$sImportFile, $sBatchEnd, $iRet);
309             if ($iRet == 5) {
310                 echo "Diff file is empty. skipping import.\n";
311                 if (!$aResult['import-osmosis-all']) {
312                     exit(0);
313                 } else {
314                     continue;
315                 }
316             }
317             if ($iRet != 0) {
318                 fail('Error getting date from diff file.');
319             }
320             $sBatchEnd = $sBatchEnd[0];
321
322             // Import the file
323             $fCMDStartTime = time();
324             echo $sCMDImport."\n";
325             unset($sJunk);
326             exec($sCMDImport, $sJunk, $iErrorLevel);
327             if ($iErrorLevel) {
328                 echo "Error executing osm2pgsql: $iErrorLevel\n";
329                 exit($iErrorLevel);
330             }
331
332             // write the update logs
333             $iFileSize = filesize($sImportFile);
334             $sSQL = 'INSERT INTO import_osmosis_log';
335             $sSQL .= '(batchend, batchseq, batchsize, starttime, endtime, event)';
336             $sSQL .= " values ('$sBatchEnd',$iEndSequence,$iFileSize,'";
337             $sSQL .= date('Y-m-d H:i:s', $fCMDStartTime)."','";
338             $sSQL .= date('Y-m-d H:i:s')."','import')";
339             var_Dump($sSQL);
340             chksql($oDB->query($sSQL));
341
342             // update the status
343             $sSQL = "UPDATE import_status SET lastimportdate = '$sBatchEnd', indexed=false, sequence_id = $iEndSequence";
344             var_Dump($sSQL);
345             chksql($oDB->query($sSQL));
346             echo date('Y-m-d H:i:s')." Completed download step for $sBatchEnd in ".round((time()-$fCMDStartTime)/60, 2)." minutes\n";
347         }
348
349         // Index file
350         if (!$aResult['no-index']) {
351             $sThisIndexCmd = $sCMDIndex;
352             $fCMDStartTime = time();
353
354             echo "$sThisIndexCmd\n";
355             exec($sThisIndexCmd, $sJunk, $iErrorLevel);
356             if ($iErrorLevel) {
357                 echo "Error: $iErrorLevel\n";
358                 exit($iErrorLevel);
359             }
360
361             $sSQL = 'INSERT INTO import_osmosis_log';
362             $sSQL .= '(batchend, batchseq, batchsize, starttime, endtime, event)';
363             $sSQL .= " values ('$sBatchEnd',$iEndSequence,$iFileSize,'";
364             $sSQL .= date('Y-m-d H:i:s', $fCMDStartTime)."','";
365             $sSQL .= date('Y-m-d H:i:s')."','index')";
366             var_Dump($sSQL);
367             $oDB->query($sSQL);
368             echo date('Y-m-d H:i:s')." Completed index step for $sBatchEnd in ".round((time()-$fCMDStartTime)/60, 2)." minutes\n";
369
370             $sSQL = 'update import_status set indexed = true';
371             $oDB->query($sSQL);
372         }
373
374         $fDuration = time() - $fStartTime;
375         echo date('Y-m-d H:i:s')." Completed all for $sBatchEnd in ".round($fDuration/60, 2)." minutes\n";
376         if (!$aResult['import-osmosis-all']) exit(0);
377     }
378 }