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