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