]> git.openstreetmap.org Git - nominatim.git/blob - utils/setup.php
bracket spacing for if/else/for/foreach/while/switch according to PSR2 standard
[nominatim.git] / utils / setup.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 = array(
9     "Create and setup nominatim search system",
10     array('help', 'h', 0, 1, 0, 0, false, 'Show Help'),
11     array('quiet', 'q', 0, 1, 0, 0, 'bool', 'Quiet output'),
12     array('verbose', 'v', 0, 1, 0, 0, 'bool', 'Verbose output'),
13
14     array('osm-file', '', 0, 1, 1, 1, 'realpath', 'File to import'),
15     array('threads', '', 0, 1, 1, 1, 'int', 'Number of threads (where possible)'),
16
17     array('all', '', 0, 1, 0, 0, 'bool', 'Do the complete process'),
18
19     array('create-db', '', 0, 1, 0, 0, 'bool', 'Create nominatim db'),
20     array('setup-db', '', 0, 1, 0, 0, 'bool', 'Build a blank nominatim db'),
21     array('import-data', '', 0, 1, 0, 0, 'bool', 'Import a osm file'),
22     array('osm2pgsql-cache', '', 0, 1, 1, 1, 'int', 'Cache size used by osm2pgsql'),
23     array('create-functions', '', 0, 1, 0, 0, 'bool', 'Create functions'),
24     array('enable-diff-updates', '', 0, 1, 0, 0, 'bool', 'Turn on the code required to make diff updates work'),
25     array('enable-debug-statements', '', 0, 1, 0, 0, 'bool', 'Include debug warning statements in pgsql commands'),
26     array('ignore-errors', '', 0, 1, 0, 0, 'bool', 'Continue import even when errors in SQL are present (EXPERT)'),
27     array('create-tables', '', 0, 1, 0, 0, 'bool', 'Create main tables'),
28     array('create-partition-tables', '', 0, 1, 0, 0, 'bool', 'Create required partition tables'),
29     array('create-partition-functions', '', 0, 1, 0, 0, 'bool', 'Create required partition triggers'),
30     array('no-partitions', '', 0, 1, 0, 0, 'bool', "Do not partition search indices (speeds up import of single country extracts)"),
31     array('import-wikipedia-articles', '', 0, 1, 0, 0, 'bool', 'Import wikipedia article dump'),
32     array('load-data', '', 0, 1, 0, 0, 'bool', 'Copy data to live tables from import table'),
33     array('disable-token-precalc', '', 0, 1, 0, 0, 'bool', 'Disable name precalculation (EXPERT)'),
34     array('import-tiger-data', '', 0, 1, 0, 0, 'bool', 'Import tiger data (not included in \'all\')'),
35     array('calculate-postcodes', '', 0, 1, 0, 0, 'bool', 'Calculate postcode centroids'),
36     array('osmosis-init', '', 0, 1, 0, 0, 'bool', 'Generate default osmosis configuration'),
37     array('index', '', 0, 1, 0, 0, 'bool', 'Index the data'),
38     array('index-noanalyse', '', 0, 1, 0, 0, 'bool', 'Do not perform analyse operations during index (EXPERT)'),
39     array('create-search-indices', '', 0, 1, 0, 0, 'bool', 'Create additional indices required for search and update'),
40     array('drop', '', 0, 1, 0, 0, 'bool', 'Drop tables needed for updates, making the database readonly (EXPERIMENTAL)'),
41 );
42 getCmdOpt($_SERVER['argv'], $aCMDOptions, $aCMDResult, true, true);
43
44 $bDidSomething = false;
45
46 // Check if osm-file is set and points to a valid file if --all or --import-data is given
47 if ($aCMDResult['import-data'] || $aCMDResult['all']) {
48     if (!isset($aCMDResult['osm-file'])) {
49         fail('missing --osm-file for data import');
50     }
51
52     if (!file_exists($aCMDResult['osm-file'])) {
53         fail('the path supplied to --osm-file does not exist');
54     }
55
56     if (!is_readable($aCMDResult['osm-file'])) {
57         fail('osm-file "'.$aCMDResult['osm-file'].'" not readable');
58     }
59 }
60
61
62 // This is a pretty hard core default - the number of processors in the box - 1
63 $iInstances = isset($aCMDResult['threads'])?$aCMDResult['threads']:(getProcessorCount()-1);
64 if ($iInstances < 1) {
65     $iInstances = 1;
66     echo "WARNING: resetting threads to $iInstances\n";
67 }
68 if ($iInstances > getProcessorCount()) {
69     $iInstances = getProcessorCount();
70     echo "WARNING: resetting threads to $iInstances\n";
71 }
72
73 // Assume we can steal all the cache memory in the box (unless told otherwise)
74 if (isset($aCMDResult['osm2pgsql-cache'])) {
75     $iCacheMemory = $aCMDResult['osm2pgsql-cache'];
76 } else {
77     $iCacheMemory = getCacheMemoryMB();
78 }
79
80 $aDSNInfo = DB::parseDSN(CONST_Database_DSN);
81 if (!isset($aDSNInfo['port']) || !$aDSNInfo['port']) $aDSNInfo['port'] = 5432;
82
83 if ($aCMDResult['create-db'] || $aCMDResult['all']) {
84     echo "Create DB\n";
85     $bDidSomething = true;
86     $oDB = DB::connect(CONST_Database_DSN, false);
87     if (!PEAR::isError($oDB)) {
88         fail('database already exists ('.CONST_Database_DSN.')');
89     }
90     passthruCheckReturn('createdb -E UTF-8 -p '.$aDSNInfo['port'].' '.$aDSNInfo['database']);
91 }
92
93 if ($aCMDResult['setup-db'] || $aCMDResult['all']) {
94     echo "Setup DB\n";
95     $bDidSomething = true;
96     // TODO: path detection, detection memory, etc.
97
98     $oDB =& getDB();
99
100     $fPostgresVersion = getPostgresVersion($oDB);
101     echo 'Postgres version found: '.$fPostgresVersion."\n";
102
103     if ($fPostgresVersion < 9.1) {
104         fail("Minimum supported version of Postgresql is 9.1.");
105     }
106
107     pgsqlRunScript('CREATE EXTENSION IF NOT EXISTS hstore');
108     pgsqlRunScript('CREATE EXTENSION IF NOT EXISTS postgis');
109
110     // For extratags and namedetails the hstore_to_json converter is
111     // needed which is only available from Postgresql 9.3+. For older
112     // versions add a dummy function that returns nothing.
113     $iNumFunc = chksql($oDB->getOne("select count(*) from pg_proc where proname = 'hstore_to_json'"));
114
115     if ($iNumFunc == 0) {
116         pgsqlRunScript("create function hstore_to_json(dummy hstore) returns text AS 'select null::text' language sql immutable");
117         echo "WARNING: Postgresql is too old. extratags and namedetails API not available.";
118     }
119
120     $fPostgisVersion = getPostgisVersion($oDB);
121     echo 'Postgis version found: '.$fPostgisVersion."\n";
122
123     if ($fPostgisVersion < 2.1) {
124         // Function was renamed in 2.1 and throws an annoying deprecation warning
125         pgsqlRunScript('ALTER FUNCTION st_line_interpolate_point(geometry, double precision) RENAME TO ST_LineInterpolatePoint');
126     }
127
128     pgsqlRunScriptFile(CONST_BasePath.'/data/country_name.sql');
129     pgsqlRunScriptFile(CONST_BasePath.'/data/country_naturalearthdata.sql');
130     pgsqlRunScriptFile(CONST_BasePath.'/data/country_osm_grid.sql');
131     pgsqlRunScriptFile(CONST_BasePath.'/data/gb_postcode_table.sql');
132     if (file_exists(CONST_BasePath.'/data/gb_postcode_data.sql.gz')) {
133         pgsqlRunScriptFile(CONST_BasePath.'/data/gb_postcode_data.sql.gz');
134     } else {
135         echo "WARNING: external UK postcode table not found.\n";
136     }
137     if (CONST_Use_Extra_US_Postcodes) {
138         pgsqlRunScriptFile(CONST_BasePath.'/data/us_postcode.sql');
139     }
140
141     if ($aCMDResult['no-partitions']) {
142         pgsqlRunScript('update country_name set partition = 0');
143     }
144
145     // the following will be needed by create_functions later but
146     // is only defined in the subsequently called create_tables.
147     // Create dummies here that will be overwritten by the proper
148     // versions in create-tables.
149     pgsqlRunScript('CREATE TABLE place_boundingbox ()');
150     pgsqlRunScript('create type wikipedia_article_match as ()');
151 }
152
153 if ($aCMDResult['import-data'] || $aCMDResult['all']) {
154     echo "Import\n";
155     $bDidSomething = true;
156
157     $osm2pgsql = CONST_Osm2pgsql_Binary;
158     if (!file_exists($osm2pgsql)) {
159         echo "Please download and build osm2pgsql.\nIf it is already installed, check the path in your local settings (settings/local.php) file.\n";
160         fail("osm2pgsql not found in '$osm2pgsql'");
161     }
162
163     if (!is_null(CONST_Osm2pgsql_Flatnode_File)) {
164         $osm2pgsql .= ' --flat-nodes '.CONST_Osm2pgsql_Flatnode_File;
165     }
166     if (CONST_Tablespace_Osm2pgsql_Data)
167         $osm2pgsql .= ' --tablespace-slim-data '.CONST_Tablespace_Osm2pgsql_Data;
168     if (CONST_Tablespace_Osm2pgsql_Index)
169         $osm2pgsql .= ' --tablespace-slim-index '.CONST_Tablespace_Osm2pgsql_Index;
170     if (CONST_Tablespace_Place_Data)
171         $osm2pgsql .= ' --tablespace-main-data '.CONST_Tablespace_Place_Data;
172     if (CONST_Tablespace_Place_Index)
173         $osm2pgsql .= ' --tablespace-main-index '.CONST_Tablespace_Place_Index;
174     $osm2pgsql .= ' -lsc -O gazetteer --hstore --number-processes 1';
175     $osm2pgsql .= ' -C '.$iCacheMemory;
176     $osm2pgsql .= ' -P '.$aDSNInfo['port'];
177     $osm2pgsql .= ' -d '.$aDSNInfo['database'].' '.$aCMDResult['osm-file'];
178     passthruCheckReturn($osm2pgsql);
179
180     $oDB =& getDB();
181     if (!chksql($oDB->getRow('select * from place limit 1'))) {
182         fail('No Data');
183     }
184 }
185
186 if ($aCMDResult['create-functions'] || $aCMDResult['all']) {
187     echo "Functions\n";
188     $bDidSomething = true;
189     if (!file_exists(CONST_InstallPath.'/module/nominatim.so')) fail("nominatim module not built");
190     create_sql_functions($aCMDResult);
191 }
192
193 if ($aCMDResult['create-tables'] || $aCMDResult['all']) {
194     $bDidSomething = true;
195
196     echo "Tables\n";
197     $sTemplate = file_get_contents(CONST_BasePath.'/sql/tables.sql');
198     $sTemplate = str_replace('{www-user}', CONST_Database_Web_User, $sTemplate);
199     $sTemplate = replace_tablespace('{ts:address-data}',
200                                     CONST_Tablespace_Address_Data, $sTemplate);
201     $sTemplate = replace_tablespace('{ts:address-index}',
202                                     CONST_Tablespace_Address_Index, $sTemplate);
203     $sTemplate = replace_tablespace('{ts:search-data}',
204                                     CONST_Tablespace_Search_Data, $sTemplate);
205     $sTemplate = replace_tablespace('{ts:search-index}',
206                                     CONST_Tablespace_Search_Index, $sTemplate);
207     $sTemplate = replace_tablespace('{ts:aux-data}',
208                                     CONST_Tablespace_Aux_Data, $sTemplate);
209     $sTemplate = replace_tablespace('{ts:aux-index}',
210                                     CONST_Tablespace_Aux_Index, $sTemplate);
211     pgsqlRunScript($sTemplate, false);
212
213     // re-run the functions
214     echo "Functions\n";
215     create_sql_functions($aCMDResult);
216 }
217
218 if ($aCMDResult['create-partition-tables'] || $aCMDResult['all']) {
219     echo "Partition Tables\n";
220     $bDidSomething = true;
221
222     $sTemplate = file_get_contents(CONST_BasePath.'/sql/partition-tables.src.sql');
223     $sTemplate = replace_tablespace('{ts:address-data}',
224                                     CONST_Tablespace_Address_Data, $sTemplate);
225     $sTemplate = replace_tablespace('{ts:address-index}',
226                                     CONST_Tablespace_Address_Index, $sTemplate);
227     $sTemplate = replace_tablespace('{ts:search-data}',
228                                     CONST_Tablespace_Search_Data, $sTemplate);
229     $sTemplate = replace_tablespace('{ts:search-index}',
230                                     CONST_Tablespace_Search_Index, $sTemplate);
231     $sTemplate = replace_tablespace('{ts:aux-data}',
232                                     CONST_Tablespace_Aux_Data, $sTemplate);
233     $sTemplate = replace_tablespace('{ts:aux-index}',
234                                     CONST_Tablespace_Aux_Index, $sTemplate);
235
236     pgsqlRunPartitionScript($sTemplate);
237 }
238
239
240 if ($aCMDResult['create-partition-functions'] || $aCMDResult['all']) {
241     echo "Partition Functions\n";
242     $bDidSomething = true;
243
244     $sTemplate = file_get_contents(CONST_BasePath.'/sql/partition-functions.src.sql');
245
246     pgsqlRunPartitionScript($sTemplate);
247 }
248
249 if ($aCMDResult['import-wikipedia-articles'] || $aCMDResult['all']) {
250     $bDidSomething = true;
251     $sWikiArticlesFile = CONST_BasePath.'/data/wikipedia_article.sql.bin';
252     $sWikiRedirectsFile = CONST_BasePath.'/data/wikipedia_redirect.sql.bin';
253     if (file_exists($sWikiArticlesFile)) {
254         echo "Importing wikipedia articles...";
255         pgsqlRunDropAndRestore($sWikiArticlesFile);
256         echo "...done\n";
257     } else {
258         echo "WARNING: wikipedia article dump file not found - places will have default importance\n";
259     }
260     if (file_exists($sWikiRedirectsFile)) {
261         echo "Importing wikipedia redirects...";
262         pgsqlRunDropAndRestore($sWikiRedirectsFile);
263         echo "...done\n";
264     } else {
265         echo "WARNING: wikipedia redirect dump file not found - some place importance values may be missing\n";
266     }
267 }
268
269
270 if ($aCMDResult['load-data'] || $aCMDResult['all']) {
271     echo "Drop old Data\n";
272     $bDidSomething = true;
273
274     $oDB =& getDB();
275     if (!pg_query($oDB->connection, 'TRUNCATE word')) fail(pg_last_error($oDB->connection));
276     echo '.';
277     if (!pg_query($oDB->connection, 'TRUNCATE placex')) fail(pg_last_error($oDB->connection));
278     echo '.';
279     if (!pg_query($oDB->connection, 'TRUNCATE location_property_osmline')) fail(pg_last_error($oDB->connection));
280     echo '.';
281     if (!pg_query($oDB->connection, 'TRUNCATE place_addressline')) fail(pg_last_error($oDB->connection));
282     echo '.';
283     if (!pg_query($oDB->connection, 'TRUNCATE place_boundingbox')) fail(pg_last_error($oDB->connection));
284     echo '.';
285     if (!pg_query($oDB->connection, 'TRUNCATE location_area')) fail(pg_last_error($oDB->connection));
286     echo '.';
287     if (!pg_query($oDB->connection, 'TRUNCATE search_name')) fail(pg_last_error($oDB->connection));
288     echo '.';
289     if (!pg_query($oDB->connection, 'TRUNCATE search_name_blank')) fail(pg_last_error($oDB->connection));
290     echo '.';
291     if (!pg_query($oDB->connection, 'DROP SEQUENCE seq_place')) fail(pg_last_error($oDB->connection));
292     echo '.';
293     if (!pg_query($oDB->connection, 'CREATE SEQUENCE seq_place start 100000')) fail(pg_last_error($oDB->connection));
294     echo '.';
295
296     $sSQL = 'select distinct partition from country_name';
297     $aPartitions = chksql($oDB->getCol($sSQL));
298     if (!$aCMDResult['no-partitions']) $aPartitions[] = 0;
299     foreach ($aPartitions as $sPartition) {
300         if (!pg_query($oDB->connection, 'TRUNCATE location_road_'.$sPartition)) fail(pg_last_error($oDB->connection));
301         echo '.';
302     }
303
304     // used by getorcreate_word_id to ignore frequent partial words
305     if (!pg_query($oDB->connection, 'CREATE OR REPLACE FUNCTION get_maxwordfreq() RETURNS integer AS $$ SELECT '.CONST_Max_Word_Frequency.' as maxwordfreq; $$ LANGUAGE SQL IMMUTABLE')) fail(pg_last_error($oDB->connection));
306     echo ".\n";
307
308     // pre-create the word list
309     if (!$aCMDResult['disable-token-precalc']) {
310         echo "Loading word list\n";
311         pgsqlRunScriptFile(CONST_BasePath.'/data/words.sql');
312     }
313
314     echo "Load Data\n";
315     $aDBInstances = array();
316     $iLoadThreads = max(1, $iInstances - 1);
317     for ($i = 0; $i < $iLoadThreads; $i++) {
318         $aDBInstances[$i] =& getDB(true);
319         $sSQL = 'insert into placex (osm_type, osm_id, class, type, name, admin_level, ';
320         $sSQL .= 'housenumber, street, addr_place, isin, postcode, country_code, extratags, ';
321         $sSQL .= 'geometry) select * from place where osm_id % '.$iLoadThreads.' = '.$i;
322         $sSQL .= " and not (class='place' and type='houses' and osm_type='W' and ST_GeometryType(geometry) = 'ST_LineString')";
323         if ($aCMDResult['verbose']) echo "$sSQL\n";
324         if (!pg_send_query($aDBInstances[$i]->connection, $sSQL)) fail(pg_last_error($oDB->connection));
325     }
326     // last thread for interpolation lines
327     $aDBInstances[$iLoadThreads] =& getDB(true);
328     $sSQL = 'select insert_osmline (osm_id, housenumber, street, addr_place, postcode, country_code, ';
329     $sSQL .= 'geometry) from place where ';
330     $sSQL .= "class='place' and type='houses' and osm_type='W' and ST_GeometryType(geometry) = 'ST_LineString'";
331     if ($aCMDResult['verbose']) echo "$sSQL\n";
332     if (!pg_send_query($aDBInstances[$i]->connection, $sSQL)) fail(pg_last_error($oDB->connection));
333
334     $bAnyBusy = true;
335     while ($bAnyBusy) {
336         $bAnyBusy = false;
337         for ($i = 0; $i <= $iLoadThreads; $i++) {
338             if (pg_connection_busy($aDBInstances[$i]->connection)) $bAnyBusy = true;
339         }
340         sleep(1);
341         echo '.';
342     }
343     echo "\n";
344     echo "Reanalysing database...\n";
345     pgsqlRunScript('ANALYSE');
346 }
347
348 if ($aCMDResult['import-tiger-data']) {
349     $bDidSomething = true;
350
351     $sTemplate = file_get_contents(CONST_BasePath.'/sql/tiger_import_start.sql');
352     $sTemplate = str_replace('{www-user}', CONST_Database_Web_User, $sTemplate);
353     $sTemplate = replace_tablespace('{ts:aux-data}',
354                                     CONST_Tablespace_Aux_Data, $sTemplate);
355     $sTemplate = replace_tablespace('{ts:aux-index}',
356                                     CONST_Tablespace_Aux_Index, $sTemplate);
357     pgsqlRunScript($sTemplate, false);
358
359     $aDBInstances = array();
360     for ($i = 0; $i < $iInstances; $i++) {
361         $aDBInstances[$i] =& getDB(true);
362     }
363
364     foreach (glob(CONST_Tiger_Data_Path.'/*.sql') as $sFile) {
365         echo $sFile.': ';
366         $hFile = fopen($sFile, "r");
367         $sSQL = fgets($hFile, 100000);
368         $iLines = 0;
369
370         while (true) {
371             for ($i = 0; $i < $iInstances; $i++) {
372                 if (!pg_connection_busy($aDBInstances[$i]->connection)) {
373                     while (pg_get_result($aDBInstances[$i]->connection));
374                     $sSQL = fgets($hFile, 100000);
375                     if (!$sSQL) break 2;
376                     if (!pg_send_query($aDBInstances[$i]->connection, $sSQL)) fail(pg_last_error($oDB->connection));
377                     $iLines++;
378                     if ($iLines == 1000) {
379                         echo ".";
380                         $iLines = 0;
381                     }
382                 }
383             }
384             usleep(10);
385         }
386
387         fclose($hFile);
388
389         $bAnyBusy = true;
390         while ($bAnyBusy) {
391             $bAnyBusy = false;
392             for ($i = 0; $i < $iInstances; $i++) {
393                 if (pg_connection_busy($aDBInstances[$i]->connection)) $bAnyBusy = true;
394             }
395             usleep(10);
396         }
397         echo "\n";
398     }
399
400     echo "Creating indexes\n";
401     $sTemplate = file_get_contents(CONST_BasePath.'/sql/tiger_import_finish.sql');
402     $sTemplate = str_replace('{www-user}', CONST_Database_Web_User, $sTemplate);
403     $sTemplate = replace_tablespace('{ts:aux-data}',
404                                     CONST_Tablespace_Aux_Data, $sTemplate);
405     $sTemplate = replace_tablespace('{ts:aux-index}',
406                                     CONST_Tablespace_Aux_Index, $sTemplate);
407     pgsqlRunScript($sTemplate, false);
408 }
409
410 if ($aCMDResult['calculate-postcodes'] || $aCMDResult['all']) {
411     $bDidSomething = true;
412     $oDB =& getDB();
413     if (!pg_query($oDB->connection, 'DELETE from placex where osm_type=\'P\'')) fail(pg_last_error($oDB->connection));
414     $sSQL = "insert into placex (osm_type,osm_id,class,type,postcode,calculated_country_code,geometry) ";
415     $sSQL .= "select 'P',nextval('seq_postcodes'),'place','postcode',postcode,calculated_country_code,";
416     $sSQL .= "ST_SetSRID(ST_Point(x,y),4326) as geometry from (select calculated_country_code,postcode,";
417     $sSQL .= "avg(st_x(st_centroid(geometry))) as x,avg(st_y(st_centroid(geometry))) as y ";
418     $sSQL .= "from placex where postcode is not null group by calculated_country_code,postcode) as x";
419     if (!pg_query($oDB->connection, $sSQL)) fail(pg_last_error($oDB->connection));
420
421     if (CONST_Use_Extra_US_Postcodes) {
422         $sSQL = "insert into placex (osm_type,osm_id,class,type,postcode,calculated_country_code,geometry) ";
423         $sSQL .= "select 'P',nextval('seq_postcodes'),'place','postcode',postcode,'us',";
424         $sSQL .= "ST_SetSRID(ST_Point(x,y),4326) as geometry from us_postcode";
425         if (!pg_query($oDB->connection, $sSQL)) fail(pg_last_error($oDB->connection));
426     }
427 }
428
429 if ($aCMDResult['osmosis-init'] || ($aCMDResult['all'] && !$aCMDResult['drop'])) { // no use doing osmosis-init when dropping update tables
430     $bDidSomething = true;
431     $oDB =& getDB();
432
433     if (!file_exists(CONST_Osmosis_Binary)) {
434         echo "Please download osmosis.\nIf it is already installed, check the path in your local settings (settings/local.php) file.\n";
435         if (!$aCMDResult['all']) {
436             fail("osmosis not found in '".CONST_Osmosis_Binary."'");
437         }
438     } else {
439         if (file_exists(CONST_InstallPath.'/settings/configuration.txt')) {
440             echo "settings/configuration.txt already exists\n";
441         } else {
442             passthru(CONST_Osmosis_Binary.' --read-replication-interval-init '.CONST_InstallPath.'/settings');
443             // update osmosis configuration.txt with our settings
444             passthru("sed -i 's!baseUrl=.*!baseUrl=".CONST_Replication_Url."!' ".CONST_InstallPath.'/settings/configuration.txt');
445             passthru("sed -i 's:maxInterval = .*:maxInterval = ".CONST_Replication_MaxInterval.":' ".CONST_InstallPath.'/settings/configuration.txt');
446         }
447
448         // Find the last node in the DB
449         $iLastOSMID = $oDB->getOne("select max(osm_id) from place where osm_type = 'N'");
450
451         // Lookup the timestamp that node was created (less 3 hours for margin for changsets to be closed)
452         $sLastNodeURL = 'http://www.openstreetmap.org/api/0.6/node/'.$iLastOSMID."/1";
453         $sLastNodeXML = file_get_contents($sLastNodeURL);
454         preg_match('#timestamp="(([0-9]{4})-([0-9]{2})-([0-9]{2})T([0-9]{2}):([0-9]{2}):([0-9]{2})Z)"#', $sLastNodeXML, $aLastNodeDate);
455         $iLastNodeTimestamp = strtotime($aLastNodeDate[1]) - (3*60*60);
456
457         // Search for the correct state file - uses file timestamps so need to sort by date descending
458         $sRepURL = CONST_Replication_Url."/";
459         $sRep = file_get_contents($sRepURL."?C=M;O=D;F=1");
460         // download.geofabrik.de:    <a href="000/">000/</a></td><td align="right">26-Feb-2013 11:53  </td>
461         // planet.openstreetmap.org: <a href="273/">273/</a>                    2013-03-11 07:41    -
462         preg_match_all('#<a href="[0-9]{3}/">([0-9]{3}/)</a>\s*([-0-9a-zA-Z]+ [0-9]{2}:[0-9]{2})#', $sRep, $aRepMatches, PREG_SET_ORDER);
463         if ($aRepMatches) {
464             $aPrevRepMatch = false;
465             foreach ($aRepMatches as $aRepMatch) {
466                 if (strtotime($aRepMatch[2]) < $iLastNodeTimestamp) break;
467                 $aPrevRepMatch = $aRepMatch;
468             }
469             if ($aPrevRepMatch) $aRepMatch = $aPrevRepMatch;
470
471             $sRepURL .= $aRepMatch[1];
472             $sRep = file_get_contents($sRepURL."?C=M;O=D;F=1");
473             preg_match_all('#<a href="[0-9]{3}/">([0-9]{3}/)</a>\s*([-0-9a-zA-Z]+ [0-9]{2}:[0-9]{2})#', $sRep, $aRepMatches, PREG_SET_ORDER);
474             $aPrevRepMatch = false;
475             foreach ($aRepMatches as $aRepMatch) {
476                 if (strtotime($aRepMatch[2]) < $iLastNodeTimestamp) break;
477                 $aPrevRepMatch = $aRepMatch;
478             }
479             if ($aPrevRepMatch) $aRepMatch = $aPrevRepMatch;
480
481             $sRepURL .= $aRepMatch[1];
482             $sRep = file_get_contents($sRepURL."?C=M;O=D;F=1");
483             preg_match_all('#<a href="[0-9]{3}.state.txt">([0-9]{3}).state.txt</a>\s*([-0-9a-zA-Z]+ [0-9]{2}:[0-9]{2})#', $sRep, $aRepMatches, PREG_SET_ORDER);
484             $aPrevRepMatch = false;
485             foreach ($aRepMatches as $aRepMatch) {
486                 if (strtotime($aRepMatch[2]) < $iLastNodeTimestamp) break;
487                 $aPrevRepMatch = $aRepMatch;
488             }
489             if ($aPrevRepMatch) $aRepMatch = $aPrevRepMatch;
490
491             $sRepURL .= $aRepMatch[1].'.state.txt';
492             echo "Getting state file: $sRepURL\n";
493             $sStateFile = file_get_contents($sRepURL);
494             if (!$sStateFile || strlen($sStateFile) > 1000) fail("unable to obtain state file");
495             file_put_contents(CONST_InstallPath.'/settings/state.txt', $sStateFile);
496             echo "Updating DB status\n";
497             pg_query($oDB->connection, 'TRUNCATE import_status');
498             $sSQL = "INSERT INTO import_status VALUES('".$aRepMatch[2]."')";
499             pg_query($oDB->connection, $sSQL);
500         } else {
501             if (!$aCMDResult['all']) {
502                 fail("Cannot read state file directory.");
503             }
504         }
505     }
506 }
507
508 if ($aCMDResult['index'] || $aCMDResult['all']) {
509     $bDidSomething = true;
510     $sOutputFile = '';
511     $sBaseCmd = CONST_InstallPath.'/nominatim/nominatim -i -d '.$aDSNInfo['database'].' -P '.$aDSNInfo['port'].' -t '.$iInstances.$sOutputFile;
512     passthruCheckReturn($sBaseCmd.' -R 4');
513     if (!$aCMDResult['index-noanalyse']) pgsqlRunScript('ANALYSE');
514     passthruCheckReturn($sBaseCmd.' -r 5 -R 25');
515     if (!$aCMDResult['index-noanalyse']) pgsqlRunScript('ANALYSE');
516     passthruCheckReturn($sBaseCmd.' -r 26');
517 }
518
519 if ($aCMDResult['create-search-indices'] || $aCMDResult['all']) {
520     echo "Search indices\n";
521     $bDidSomething = true;
522
523     $sTemplate = file_get_contents(CONST_BasePath.'/sql/indices.src.sql');
524     $sTemplate = replace_tablespace('{ts:address-index}',
525                                     CONST_Tablespace_Address_Index, $sTemplate);
526     $sTemplate = replace_tablespace('{ts:search-index}',
527                                     CONST_Tablespace_Search_Index, $sTemplate);
528     $sTemplate = replace_tablespace('{ts:aux-index}',
529                                     CONST_Tablespace_Aux_Index, $sTemplate);
530
531     pgsqlRunScript($sTemplate);
532 }
533
534 if ($aCMDResult['drop']) {
535     // The implementation is potentially a bit dangerous because it uses
536     // a positive selection of tables to keep, and deletes everything else.
537     // Including any tables that the unsuspecting user might have manually
538     // created. USE AT YOUR OWN PERIL.
539     $bDidSomething = true;
540
541     // tables we want to keep. everything else goes.
542     $aKeepTables = array(
543        "*columns",
544        "import_polygon_*",
545        "import_status",
546        "place_addressline",
547        "location_property*",
548        "placex",
549        "search_name",
550        "seq_*",
551        "word",
552        "query_log",
553        "new_query_log",
554        "gb_postcode",
555        "spatial_ref_sys",
556        "country_name",
557        "place_classtype_*"
558     );
559
560     $oDB =& getDB();
561     $aDropTables = array();
562     $aHaveTables = chksql($oDB->getCol("SELECT tablename FROM pg_tables WHERE schemaname='public'"));
563
564     foreach ($aHaveTables as $sTable) {
565         $bFound = false;
566         foreach ($aKeepTables as $sKeep) {
567             if (fnmatch($sKeep, $sTable)) {
568                 $bFound = true;
569                 break;
570             }
571         }
572         if (!$bFound) array_push($aDropTables, $sTable);
573     }
574
575     foreach ($aDropTables as $sDrop) {
576         if ($aCMDResult['verbose']) echo "dropping table $sDrop\n";
577         @pg_query($oDB->connection, "DROP TABLE $sDrop CASCADE");
578         // ignore warnings/errors as they might be caused by a table having
579         // been deleted already by CASCADE
580     }
581
582     if (!is_null(CONST_Osm2pgsql_Flatnode_File)) {
583         if ($aCMDResult['verbose']) echo "deleting ".CONST_Osm2pgsql_Flatnode_File."\n";
584         unlink(CONST_Osm2pgsql_Flatnode_File);
585     }
586 }
587
588 if (!$bDidSomething) {
589     showUsage($aCMDOptions, true);
590 } else {
591     echo "Setup finished.\n";
592 }
593
594 function pgsqlRunScriptFile($sFilename)
595 {
596     if (!file_exists($sFilename)) fail('unable to find '.$sFilename);
597
598     // Convert database DSN to psql parameters
599     $aDSNInfo = DB::parseDSN(CONST_Database_DSN);
600     if (!isset($aDSNInfo['port']) || !$aDSNInfo['port']) $aDSNInfo['port'] = 5432;
601     $sCMD = 'psql -p '.$aDSNInfo['port'].' -d '.$aDSNInfo['database'];
602
603     $ahGzipPipes = null;
604     if (preg_match('/\\.gz$/', $sFilename)) {
605         $aDescriptors = array(
606             0 => array('pipe', 'r'),
607             1 => array('pipe', 'w'),
608             2 => array('file', '/dev/null', 'a')
609         );
610         $hGzipProcess = proc_open('zcat '.$sFilename, $aDescriptors, $ahGzipPipes);
611         if (!is_resource($hGzipProcess)) fail('unable to start zcat');
612         $aReadPipe = $ahGzipPipes[1];
613         fclose($ahGzipPipes[0]);
614     } else {
615         $sCMD .= ' -f '.$sFilename;
616         $aReadPipe = array('pipe', 'r');
617     }
618
619     $aDescriptors = array(
620         0 => $aReadPipe,
621         1 => array('pipe', 'w'),
622         2 => array('file', '/dev/null', 'a')
623     );
624     $ahPipes = null;
625     $hProcess = proc_open($sCMD, $aDescriptors, $ahPipes);
626     if (!is_resource($hProcess)) fail('unable to start pgsql');
627
628
629     // TODO: error checking
630     while (!feof($ahPipes[1])) {
631         echo fread($ahPipes[1], 4096);
632     }
633     fclose($ahPipes[1]);
634
635     $iReturn = proc_close($hProcess);
636     if ($iReturn > 0) {
637         fail("pgsql returned with error code ($iReturn)");
638     }
639     if ($ahGzipPipes) {
640         fclose($ahGzipPipes[1]);
641         proc_close($hGzipProcess);
642     }
643
644 }
645
646 function pgsqlRunScript($sScript, $bfatal = true)
647 {
648     global $aCMDResult;
649     // Convert database DSN to psql parameters
650     $aDSNInfo = DB::parseDSN(CONST_Database_DSN);
651     if (!isset($aDSNInfo['port']) || !$aDSNInfo['port']) $aDSNInfo['port'] = 5432;
652     $sCMD = 'psql -p '.$aDSNInfo['port'].' -d '.$aDSNInfo['database'];
653     if ($bfatal && !$aCMDResult['ignore-errors'])
654         $sCMD .= ' -v ON_ERROR_STOP=1';
655     $aDescriptors = array(
656         0 => array('pipe', 'r'),
657         1 => STDOUT, 
658         2 => STDERR
659     );
660     $ahPipes = null;
661     $hProcess = @proc_open($sCMD, $aDescriptors, $ahPipes);
662     if (!is_resource($hProcess)) fail('unable to start pgsql');
663
664     while (strlen($sScript)) {
665         $written = fwrite($ahPipes[0], $sScript);
666         if ($written <= 0) break;
667         $sScript = substr($sScript, $written);
668     }
669     fclose($ahPipes[0]);
670     $iReturn = proc_close($hProcess);
671     if ($bfatal && $iReturn > 0) {
672         fail("pgsql returned with error code ($iReturn)");
673     }
674 }
675
676 function pgsqlRunPartitionScript($sTemplate)
677 {
678     global $aCMDResult;
679     $oDB =& getDB();
680
681     $sSQL = 'select distinct partition from country_name';
682     $aPartitions = chksql($oDB->getCol($sSQL));
683     if (!$aCMDResult['no-partitions']) $aPartitions[] = 0;
684
685     preg_match_all('#^-- start(.*?)^-- end#ms', $sTemplate, $aMatches, PREG_SET_ORDER);
686     foreach ($aMatches as $aMatch) {
687         $sResult = '';
688         foreach ($aPartitions as $sPartitionName) {
689             $sResult .= str_replace('-partition-', $sPartitionName, $aMatch[1]);
690         }
691         $sTemplate = str_replace($aMatch[0], $sResult, $sTemplate);
692     }
693
694     pgsqlRunScript($sTemplate);
695 }
696
697 function pgsqlRunRestoreData($sDumpFile)
698 {
699     // Convert database DSN to psql parameters
700     $aDSNInfo = DB::parseDSN(CONST_Database_DSN);
701     if (!isset($aDSNInfo['port']) || !$aDSNInfo['port']) $aDSNInfo['port'] = 5432;
702     $sCMD = 'pg_restore -p '.$aDSNInfo['port'].' -d '.$aDSNInfo['database'].' -Fc -a '.$sDumpFile;
703
704     $aDescriptors = array(
705         0 => array('pipe', 'r'),
706         1 => array('pipe', 'w'),
707         2 => array('file', '/dev/null', 'a')
708     );
709     $ahPipes = null;
710     $hProcess = proc_open($sCMD, $aDescriptors, $ahPipes);
711     if (!is_resource($hProcess)) fail('unable to start pg_restore');
712
713     fclose($ahPipes[0]);
714
715     // TODO: error checking
716     while (!feof($ahPipes[1])) {
717         echo fread($ahPipes[1], 4096);
718     }
719     fclose($ahPipes[1]);
720
721     $iReturn = proc_close($hProcess);
722 }
723
724 function pgsqlRunDropAndRestore($sDumpFile)
725 {
726     // Convert database DSN to psql parameters
727     $aDSNInfo = DB::parseDSN(CONST_Database_DSN);
728     if (!isset($aDSNInfo['port']) || !$aDSNInfo['port']) $aDSNInfo['port'] = 5432;
729     $sCMD = 'pg_restore -p '.$aDSNInfo['port'].' -d '.$aDSNInfo['database'].' -Fc --clean '.$sDumpFile;
730
731     $aDescriptors = array(
732         0 => array('pipe', 'r'),
733         1 => array('pipe', 'w'),
734         2 => array('file', '/dev/null', 'a')
735     );
736     $ahPipes = null;
737     $hProcess = proc_open($sCMD, $aDescriptors, $ahPipes);
738     if (!is_resource($hProcess)) fail('unable to start pg_restore');
739
740     fclose($ahPipes[0]);
741
742     // TODO: error checking
743     while (!feof($ahPipes[1])) {
744         echo fread($ahPipes[1], 4096);
745     }
746     fclose($ahPipes[1]);
747
748     $iReturn = proc_close($hProcess);
749 }
750
751 function passthruCheckReturn($cmd)
752 {
753     $result = -1;
754     passthru($cmd, $result);
755     if ($result != 0) fail('Error executing external command: '.$cmd);
756 }
757
758 function replace_tablespace($sTemplate, $sTablespace, $sSql)
759 {
760     if ($sTablespace) {
761         $sSql = str_replace($sTemplate, 'TABLESPACE "'.$sTablespace.'"',
762                             $sSql);
763     } else {
764         $sSql = str_replace($sTemplate, '', $sSql);
765     }
766
767     return $sSql;
768 }
769
770 function create_sql_functions($aCMDResult)
771 {
772     $sTemplate = file_get_contents(CONST_BasePath.'/sql/functions.sql');
773     $sTemplate = str_replace('{modulepath}', CONST_InstallPath.'/module', $sTemplate);
774     if ($aCMDResult['enable-diff-updates']) {
775         $sTemplate = str_replace('RETURN NEW; -- %DIFFUPDATES%', '--', $sTemplate);
776     }
777     if ($aCMDResult['enable-debug-statements']) {
778         $sTemplate = str_replace('--DEBUG:', '', $sTemplate);
779     }
780     if (CONST_Limit_Reindexing) {
781         $sTemplate = str_replace('--LIMIT INDEXING:', '', $sTemplate);
782     }
783     if (!CONST_Use_US_Tiger_Data) {
784         $sTemplate = str_replace('-- %NOTIGERDATA% ', '', $sTemplate);
785     }
786     if (!CONST_Use_Aux_Location_data) {
787         $sTemplate = str_replace('-- %NOAUXDATA% ', '', $sTemplate);
788     }
789     pgsqlRunScript($sTemplate);
790
791 }
792