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