4 require_once(dirname(dirname(__FILE__)).'/settings/settings.php');
5 require_once(CONST_BasePath.'/lib/init-cmd.php');
6 ini_set('memory_limit', '800M');
9 "Create and setup nominatim search system",
10 array('help', 'h', 0, 1, 0, 0, false, 'Show Help'),
11 array('quiet', 'q', 0, 1, 0, 0, 'bool', 'Quiet output'),
12 array('verbose', 'v', 0, 1, 0, 0, 'bool', 'Verbose output'),
14 array('osm-file', '', 0, 1, 1, 1, 'realpath', 'File to import'),
15 array('threads', '', 0, 1, 1, 1, 'int', 'Number of threads (where possible)'),
17 array('all', '', 0, 1, 0, 0, 'bool', 'Do the complete process'),
19 array('create-db', '', 0, 1, 0, 0, 'bool', 'Create nominatim db'),
20 array('setup-db', '', 0, 1, 0, 0, 'bool', 'Build a blank nominatim db'),
21 array('import-data', '', 0, 1, 0, 0, 'bool', 'Import a osm file'),
22 array('osm2pgsql-cache', '', 0, 1, 1, 1, 'int', 'Cache size used by osm2pgsql'),
23 array('create-functions', '', 0, 1, 0, 0, 'bool', 'Create functions'),
24 array('enable-diff-updates', '', 0, 1, 0, 0, 'bool', 'Turn on the code required to make diff updates work'),
25 array('enable-debug-statements', '', 0, 1, 0, 0, 'bool', 'Include debug warning statements in pgsql commands'),
26 array('ignore-errors', '', 0, 1, 0, 0, 'bool', 'Continue import even when errors in SQL are present (EXPERT)'),
27 array('create-tables', '', 0, 1, 0, 0, 'bool', 'Create main tables'),
28 array('create-partition-tables', '', 0, 1, 0, 0, 'bool', 'Create required partition tables'),
29 array('create-partition-functions', '', 0, 1, 0, 0, 'bool', 'Create required partition triggers'),
30 array('no-partitions', '', 0, 1, 0, 0, 'bool', "Do not partition search indices (speeds up import of single country extracts)"),
31 array('import-wikipedia-articles', '', 0, 1, 0, 0, 'bool', 'Import wikipedia article dump'),
32 array('load-data', '', 0, 1, 0, 0, 'bool', 'Copy data to live tables from import table'),
33 array('disable-token-precalc', '', 0, 1, 0, 0, 'bool', 'Disable name precalculation (EXPERT)'),
34 array('import-tiger-data', '', 0, 1, 0, 0, 'bool', 'Import tiger data (not included in \'all\')'),
35 array('calculate-postcodes', '', 0, 1, 0, 0, 'bool', 'Calculate postcode centroids'),
36 array('osmosis-init', '', 0, 1, 0, 0, 'bool', 'Generate default osmosis configuration'),
37 array('index', '', 0, 1, 0, 0, 'bool', 'Index the data'),
38 array('index-noanalyse', '', 0, 1, 0, 0, 'bool', 'Do not perform analyse operations during index (EXPERT)'),
39 array('create-search-indices', '', 0, 1, 0, 0, 'bool', 'Create additional indices required for search and update'),
40 array('create-website', '', 0, 1, 1, 1, 'realpath', 'Create symlinks to setup web directory'),
41 array('drop', '', 0, 1, 0, 0, 'bool', 'Drop tables needed for updates, making the database readonly (EXPERIMENTAL)'),
43 getCmdOpt($_SERVER['argv'], $aCMDOptions, $aCMDResult, true, true);
45 $bDidSomething = false;
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'])
50 if (!isset($aCMDResult['osm-file']))
52 fail('missing --osm-file for data import');
55 if (!file_exists($aCMDResult['osm-file']))
57 fail('the path supplied to --osm-file does not exist');
60 if (!is_readable($aCMDResult['osm-file']))
62 fail('osm-file "'.$aCMDResult['osm-file'].'" not readable');
67 // This is a pretty hard core default - the number of processors in the box - 1
68 $iInstances = isset($aCMDResult['threads'])?$aCMDResult['threads']:(getProcessorCount()-1);
72 echo "WARNING: resetting threads to $iInstances\n";
74 if ($iInstances > getProcessorCount())
76 $iInstances = getProcessorCount();
77 echo "WARNING: resetting threads to $iInstances\n";
80 // Assume we can steal all the cache memory in the box (unless told otherwise)
81 if (isset($aCMDResult['osm2pgsql-cache']))
83 $iCacheMemory = $aCMDResult['osm2pgsql-cache'];
87 $iCacheMemory = getCacheMemoryMB();
90 $aDSNInfo = DB::parseDSN(CONST_Database_DSN);
91 if (!isset($aDSNInfo['port']) || !$aDSNInfo['port']) $aDSNInfo['port'] = 5432;
93 if ($aCMDResult['create-db'] || $aCMDResult['all'])
96 $bDidSomething = true;
97 $oDB =& DB::connect(CONST_Database_DSN, false);
98 if (!PEAR::isError($oDB))
100 fail('database already exists ('.CONST_Database_DSN.')');
102 passthruCheckReturn('createdb -E UTF-8 -p '.$aDSNInfo['port'].' '.$aDSNInfo['database']);
105 if ($aCMDResult['setup-db'] || $aCMDResult['all'])
108 $bDidSomething = true;
109 // TODO: path detection, detection memory, etc.
113 $fPostgresVersion = getPostgresVersion($oDB);
114 echo 'Postgres version found: '.$fPostgresVersion."\n";
116 if ($fPostgresVersion < 9.1)
118 fail("Minimum supported version of Postgresql is 9.1.");
121 pgsqlRunScript('CREATE EXTENSION IF NOT EXISTS hstore');
122 pgsqlRunScript('CREATE EXTENSION IF NOT EXISTS postgis');
124 // For extratags and namedetails the hstore_to_json converter is
125 // needed which is only available from Postgresql 9.3+. For older
126 // versions add a dummy function that returns nothing.
127 $iNumFunc = $oDB->getOne("select count(*) from pg_proc where proname = 'hstore_to_json'");
128 if (PEAR::isError($iNumFunc))
130 fail("Cannot query stored procedures.", $iNumFunc);
134 pgsqlRunScript("create function hstore_to_json(dummy hstore) returns text AS 'select null::text' language sql immutable");
135 echo "WARNING: Postgresql is too old. extratags and namedetails API not available.";
138 $fPostgisVersion = getPostgisVersion($oDB);
139 echo 'Postgis version found: '.$fPostgisVersion."\n";
141 if ($fPostgisVersion < 2.1)
143 // Function was renamed in 2.1 and throws an annoying deprecation warning
144 pgsqlRunScript('ALTER FUNCTION st_line_interpolate_point(geometry, double precision) RENAME TO ST_LineInterpolatePoint');
147 pgsqlRunScriptFile(CONST_BasePath.'/data/country_name.sql');
148 pgsqlRunScriptFile(CONST_BasePath.'/data/country_naturalearthdata.sql');
149 pgsqlRunScriptFile(CONST_BasePath.'/data/country_osm_grid.sql');
150 pgsqlRunScriptFile(CONST_BasePath.'/data/gb_postcode_table.sql');
151 if (file_exists(CONST_BasePath.'/data/gb_postcode_data.sql.gz'))
153 pgsqlRunScriptFile(CONST_BasePath.'/data/gb_postcode_data.sql.gz');
157 echo "WARNING: external UK postcode table not found.\n";
159 if (CONST_Use_Extra_US_Postcodes)
161 pgsqlRunScriptFile(CONST_BasePath.'/data/us_postcode.sql');
164 if ($aCMDResult['no-partitions'])
166 pgsqlRunScript('update country_name set partition = 0');
169 // the following will be needed by create_functions later but
170 // is only defined in the subsequently called create_tables.
171 // Create dummies here that will be overwritten by the proper
172 // versions in create-tables.
173 pgsqlRunScript('CREATE TABLE place_boundingbox ()');
174 pgsqlRunScript('create type wikipedia_article_match as ()');
177 if ($aCMDResult['import-data'] || $aCMDResult['all'])
180 $bDidSomething = true;
182 $osm2pgsql = CONST_Osm2pgsql_Binary;
183 if (!file_exists($osm2pgsql))
185 echo "Please download and build osm2pgsql.\nIf it is already installed, check the path in your local settings (settings/local.php) file.\n";
186 fail("osm2pgsql not found in '$osm2pgsql'");
189 if (!is_null(CONST_Osm2pgsql_Flatnode_File))
191 $osm2pgsql .= ' --flat-nodes '.CONST_Osm2pgsql_Flatnode_File;
193 if (CONST_Tablespace_Osm2pgsql_Data)
194 $osm2pgsql .= ' --tablespace-slim-data '.CONST_Tablespace_Osm2pgsql_Data;
195 if (CONST_Tablespace_Osm2pgsql_Index)
196 $osm2pgsql .= ' --tablespace-slim-index '.CONST_Tablespace_Osm2pgsql_Index;
197 if (CONST_Tablespace_Place_Data)
198 $osm2pgsql .= ' --tablespace-main-data '.CONST_Tablespace_Place_Data;
199 if (CONST_Tablespace_Place_Index)
200 $osm2pgsql .= ' --tablespace-main-index '.CONST_Tablespace_Place_Index;
201 $osm2pgsql .= ' -lsc -O gazetteer --hstore --number-processes 1';
202 $osm2pgsql .= ' -C '.$iCacheMemory;
203 $osm2pgsql .= ' -P '.$aDSNInfo['port'];
204 $osm2pgsql .= ' -d '.$aDSNInfo['database'].' '.$aCMDResult['osm-file'];
205 passthruCheckReturn($osm2pgsql);
208 $x = $oDB->getRow('select * from place limit 1');
209 if (PEAR::isError($x)) {
210 fail($x->getMessage());
212 if (!$x) fail('No Data');
215 if ($aCMDResult['create-functions'] || $aCMDResult['all'])
218 $bDidSomething = true;
219 if (!file_exists(CONST_InstallPath.'/module/nominatim.so')) fail("nominatim module not built");
220 $sTemplate = file_get_contents(CONST_BasePath.'/sql/functions.sql');
221 $sTemplate = str_replace('{modulepath}', CONST_InstallPath.'/module', $sTemplate);
222 if ($aCMDResult['enable-diff-updates']) $sTemplate = str_replace('RETURN NEW; -- @DIFFUPDATES@', '--', $sTemplate);
223 if ($aCMDResult['enable-debug-statements']) $sTemplate = str_replace('--DEBUG:', '', $sTemplate);
224 if (CONST_Limit_Reindexing) $sTemplate = str_replace('--LIMIT INDEXING:', '', $sTemplate);
225 pgsqlRunScript($sTemplate);
228 if ($aCMDResult['create-tables'] || $aCMDResult['all'])
230 $bDidSomething = true;
233 $sTemplate = file_get_contents(CONST_BasePath.'/sql/tables.sql');
234 $sTemplate = str_replace('{www-user}', CONST_Database_Web_User, $sTemplate);
235 $sTemplate = replace_tablespace('{ts:address-data}',
236 CONST_Tablespace_Address_Data, $sTemplate);
237 $sTemplate = replace_tablespace('{ts:address-index}',
238 CONST_Tablespace_Address_Index, $sTemplate);
239 $sTemplate = replace_tablespace('{ts:search-data}',
240 CONST_Tablespace_Search_Data, $sTemplate);
241 $sTemplate = replace_tablespace('{ts:search-index}',
242 CONST_Tablespace_Search_Index, $sTemplate);
243 $sTemplate = replace_tablespace('{ts:aux-data}',
244 CONST_Tablespace_Aux_Data, $sTemplate);
245 $sTemplate = replace_tablespace('{ts:aux-index}',
246 CONST_Tablespace_Aux_Index, $sTemplate);
247 pgsqlRunScript($sTemplate, false);
249 // re-run the functions
251 $sTemplate = file_get_contents(CONST_BasePath.'/sql/functions.sql');
252 $sTemplate = str_replace('{modulepath}',
253 CONST_InstallPath.'/module', $sTemplate);
254 pgsqlRunScript($sTemplate);
257 if ($aCMDResult['create-partition-tables'] || $aCMDResult['all'])
259 echo "Partition Tables\n";
260 $bDidSomething = true;
262 $sSQL = 'select distinct partition from country_name';
263 $aPartitions = $oDB->getCol($sSQL);
264 if (PEAR::isError($aPartitions))
266 fail($aPartitions->getMessage());
268 if (!$aCMDResult['no-partitions']) $aPartitions[] = 0;
270 $sTemplate = file_get_contents(CONST_BasePath.'/sql/partition-tables.src.sql');
271 $sTemplate = replace_tablespace('{ts:address-data}',
272 CONST_Tablespace_Address_Data, $sTemplate);
273 $sTemplate = replace_tablespace('{ts:address-index}',
274 CONST_Tablespace_Address_Index, $sTemplate);
275 $sTemplate = replace_tablespace('{ts:search-data}',
276 CONST_Tablespace_Search_Data, $sTemplate);
277 $sTemplate = replace_tablespace('{ts:search-index}',
278 CONST_Tablespace_Search_Index, $sTemplate);
279 $sTemplate = replace_tablespace('{ts:aux-data}',
280 CONST_Tablespace_Aux_Data, $sTemplate);
281 $sTemplate = replace_tablespace('{ts:aux-index}',
282 CONST_Tablespace_Aux_Index, $sTemplate);
283 preg_match_all('#^-- start(.*?)^-- end#ms', $sTemplate, $aMatches, PREG_SET_ORDER);
284 foreach($aMatches as $aMatch)
287 foreach($aPartitions as $sPartitionName)
289 $sResult .= str_replace('-partition-', $sPartitionName, $aMatch[1]);
291 $sTemplate = str_replace($aMatch[0], $sResult, $sTemplate);
294 pgsqlRunScript($sTemplate);
298 if ($aCMDResult['create-partition-functions'] || $aCMDResult['all'])
300 echo "Partition Functions\n";
301 $bDidSomething = true;
303 $sSQL = 'select distinct partition from country_name';
304 $aPartitions = $oDB->getCol($sSQL);
305 if (PEAR::isError($aPartitions))
307 fail($aPartitions->getMessage());
309 if (!$aCMDResult['no-partitions']) $aPartitions[] = 0;
311 $sTemplate = file_get_contents(CONST_BasePath.'/sql/partition-functions.src.sql');
312 preg_match_all('#^-- start(.*?)^-- end#ms', $sTemplate, $aMatches, PREG_SET_ORDER);
313 foreach($aMatches as $aMatch)
316 foreach($aPartitions as $sPartitionName)
318 $sResult .= str_replace('-partition-', $sPartitionName, $aMatch[1]);
320 $sTemplate = str_replace($aMatch[0], $sResult, $sTemplate);
323 pgsqlRunScript($sTemplate);
326 if ($aCMDResult['import-wikipedia-articles'] || $aCMDResult['all'])
328 $bDidSomething = true;
329 $sWikiArticlesFile = CONST_BasePath.'/data/wikipedia_article.sql.bin';
330 $sWikiRedirectsFile = CONST_BasePath.'/data/wikipedia_redirect.sql.bin';
331 if (file_exists($sWikiArticlesFile))
333 echo "Importing wikipedia articles...";
334 pgsqlRunDropAndRestore($sWikiArticlesFile);
339 echo "WARNING: wikipedia article dump file not found - places will have default importance\n";
341 if (file_exists($sWikiRedirectsFile))
343 echo "Importing wikipedia redirects...";
344 pgsqlRunDropAndRestore($sWikiRedirectsFile);
349 echo "WARNING: wikipedia redirect dump file not found - some place importance values may be missing\n";
354 if ($aCMDResult['load-data'] || $aCMDResult['all'])
356 echo "Drop old Data\n";
357 $bDidSomething = true;
360 if (!pg_query($oDB->connection, 'TRUNCATE word')) fail(pg_last_error($oDB->connection));
362 if (!pg_query($oDB->connection, 'TRUNCATE placex')) fail(pg_last_error($oDB->connection));
364 if (!pg_query($oDB->connection, 'TRUNCATE place_addressline')) fail(pg_last_error($oDB->connection));
366 if (!pg_query($oDB->connection, 'TRUNCATE place_boundingbox')) fail(pg_last_error($oDB->connection));
368 if (!pg_query($oDB->connection, 'TRUNCATE location_area')) fail(pg_last_error($oDB->connection));
370 if (!pg_query($oDB->connection, 'TRUNCATE search_name')) fail(pg_last_error($oDB->connection));
372 if (!pg_query($oDB->connection, 'TRUNCATE search_name_blank')) fail(pg_last_error($oDB->connection));
374 if (!pg_query($oDB->connection, 'DROP SEQUENCE seq_place')) fail(pg_last_error($oDB->connection));
376 if (!pg_query($oDB->connection, 'CREATE SEQUENCE seq_place start 100000')) fail(pg_last_error($oDB->connection));
379 $sSQL = 'select distinct partition from country_name';
380 $aPartitions = $oDB->getCol($sSQL);
381 if (PEAR::isError($aPartitions))
383 fail($aPartitions->getMessage());
385 if (!$aCMDResult['no-partitions']) $aPartitions[] = 0;
386 foreach($aPartitions as $sPartition)
388 if (!pg_query($oDB->connection, 'TRUNCATE location_road_'.$sPartition)) fail(pg_last_error($oDB->connection));
392 // used by getorcreate_word_id to ignore frequent partial words
393 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));
396 // pre-create the word list
397 if (!$aCMDResult['disable-token-precalc'])
399 echo "Loading word list\n";
400 pgsqlRunScriptFile(CONST_BasePath.'/data/words.sql');
404 $aDBInstances = array();
405 for($i = 0; $i < $iInstances; $i++)
407 $aDBInstances[$i] =& getDB(true);
408 $sSQL = 'insert into placex (osm_type, osm_id, class, type, name, admin_level, ';
409 $sSQL .= 'housenumber, street, addr_place, isin, postcode, country_code, extratags, ';
410 $sSQL .= 'geometry) select * from place where osm_id % '.$iInstances.' = '.$i;
411 if ($aCMDResult['verbose']) echo "$sSQL\n";
412 if (!pg_send_query($aDBInstances[$i]->connection, $sSQL)) fail(pg_last_error($oDB->connection));
418 for($i = 0; $i < $iInstances; $i++)
420 if (pg_connection_busy($aDBInstances[$i]->connection)) $bAnyBusy = true;
426 echo "Reanalysing database...\n";
427 pgsqlRunScript('ANALYSE');
430 if ($aCMDResult['import-tiger-data'])
432 $bDidSomething = true;
434 $sTemplate = file_get_contents(CONST_BasePath.'/sql/tiger_import_start.sql');
435 $sTemplate = str_replace('{www-user}', CONST_Database_Web_User, $sTemplate);
436 $sTemplate = replace_tablespace('{ts:aux-data}',
437 CONST_Tablespace_Aux_Data, $sTemplate);
438 $sTemplate = replace_tablespace('{ts:aux-index}',
439 CONST_Tablespace_Aux_Index, $sTemplate);
440 pgsqlRunScript($sTemplate, false);
442 $aDBInstances = array();
443 for($i = 0; $i < $iInstances; $i++)
445 $aDBInstances[$i] =& getDB(true);
448 foreach(glob(CONST_Tiger_Data_Path.'/*.sql') as $sFile)
451 $hFile = fopen($sFile, "r");
452 $sSQL = fgets($hFile, 100000);
457 for($i = 0; $i < $iInstances; $i++)
459 if (!pg_connection_busy($aDBInstances[$i]->connection))
461 while(pg_get_result($aDBInstances[$i]->connection));
462 $sSQL = fgets($hFile, 100000);
464 if (!pg_send_query($aDBInstances[$i]->connection, $sSQL)) fail(pg_last_error($oDB->connection));
482 for($i = 0; $i < $iInstances; $i++)
484 if (pg_connection_busy($aDBInstances[$i]->connection)) $bAnyBusy = true;
491 echo "Creating indexes\n";
492 $sTemplate = file_get_contents(CONST_BasePath.'/sql/tiger_import_finish.sql');
493 $sTemplate = str_replace('{www-user}', CONST_Database_Web_User, $sTemplate);
494 $sTemplate = replace_tablespace('{ts:aux-data}',
495 CONST_Tablespace_Aux_Data, $sTemplate);
496 $sTemplate = replace_tablespace('{ts:aux-index}',
497 CONST_Tablespace_Aux_Index, $sTemplate);
498 pgsqlRunScript($sTemplate, false);
501 if ($aCMDResult['calculate-postcodes'] || $aCMDResult['all'])
503 $bDidSomething = true;
505 if (!pg_query($oDB->connection, 'DELETE from placex where osm_type=\'P\'')) fail(pg_last_error($oDB->connection));
506 $sSQL = "insert into placex (osm_type,osm_id,class,type,postcode,calculated_country_code,geometry) ";
507 $sSQL .= "select 'P',nextval('seq_postcodes'),'place','postcode',postcode,calculated_country_code,";
508 $sSQL .= "ST_SetSRID(ST_Point(x,y),4326) as geometry from (select calculated_country_code,postcode,";
509 $sSQL .= "avg(st_x(st_centroid(geometry))) as x,avg(st_y(st_centroid(geometry))) as y ";
510 $sSQL .= "from placex where postcode is not null group by calculated_country_code,postcode) as x";
511 if (!pg_query($oDB->connection, $sSQL)) fail(pg_last_error($oDB->connection));
513 if (CONST_Use_Extra_US_Postcodes)
515 $sSQL = "insert into placex (osm_type,osm_id,class,type,postcode,calculated_country_code,geometry) ";
516 $sSQL .= "select 'P',nextval('seq_postcodes'),'place','postcode',postcode,'us',";
517 $sSQL .= "ST_SetSRID(ST_Point(x,y),4326) as geometry from us_postcode";
518 if (!pg_query($oDB->connection, $sSQL)) fail(pg_last_error($oDB->connection));
522 if ($aCMDResult['osmosis-init'] || ($aCMDResult['all'] && !$aCMDResult['drop'])) // no use doing osmosis-init when dropping update tables
524 $bDidSomething = true;
527 if (!file_exists(CONST_Osmosis_Binary))
529 echo "Please download osmosis.\nIf it is already installed, check the path in your local settings (settings/local.php) file.\n";
530 if (!$aCMDResult['all'])
532 fail("osmosis not found in '".CONST_Osmosis_Binary."'");
537 if (file_exists(CONST_InstallPath.'/settings/configuration.txt'))
539 echo "settings/configuration.txt already exists\n";
543 passthru(CONST_Osmosis_Binary.' --read-replication-interval-init '.CONST_InstallPath.'/settings');
544 // update osmosis configuration.txt with our settings
545 passthru("sed -i 's!baseUrl=.*!baseUrl=".CONST_Replication_Url."!' ".CONST_InstallPath.'/settings/configuration.txt');
546 passthru("sed -i 's:maxInterval = .*:maxInterval = ".CONST_Replication_MaxInterval.":' ".CONST_InstallPath.'/settings/configuration.txt');
549 // Find the last node in the DB
550 $iLastOSMID = $oDB->getOne("select max(osm_id) from place where osm_type = 'N'");
552 // Lookup the timestamp that node was created (less 3 hours for margin for changsets to be closed)
553 $sLastNodeURL = 'http://www.openstreetmap.org/api/0.6/node/'.$iLastOSMID."/1";
554 $sLastNodeXML = file_get_contents($sLastNodeURL);
555 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);
556 $iLastNodeTimestamp = strtotime($aLastNodeDate[1]) - (3*60*60);
558 // Search for the correct state file - uses file timestamps so need to sort by date descending
559 $sRepURL = CONST_Replication_Url."/";
560 $sRep = file_get_contents($sRepURL."?C=M;O=D;F=1");
561 // download.geofabrik.de: <a href="000/">000/</a></td><td align="right">26-Feb-2013 11:53 </td>
562 // planet.openstreetmap.org: <a href="273/">273/</a> 2013-03-11 07:41 -
563 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);
566 $aPrevRepMatch = false;
567 foreach($aRepMatches as $aRepMatch)
569 if (strtotime($aRepMatch[2]) < $iLastNodeTimestamp) break;
570 $aPrevRepMatch = $aRepMatch;
572 if ($aPrevRepMatch) $aRepMatch = $aPrevRepMatch;
574 $sRepURL .= $aRepMatch[1];
575 $sRep = file_get_contents($sRepURL."?C=M;O=D;F=1");
576 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);
577 $aPrevRepMatch = false;
578 foreach($aRepMatches as $aRepMatch)
580 if (strtotime($aRepMatch[2]) < $iLastNodeTimestamp) break;
581 $aPrevRepMatch = $aRepMatch;
583 if ($aPrevRepMatch) $aRepMatch = $aPrevRepMatch;
585 $sRepURL .= $aRepMatch[1];
586 $sRep = file_get_contents($sRepURL."?C=M;O=D;F=1");
587 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);
588 $aPrevRepMatch = false;
589 foreach($aRepMatches as $aRepMatch)
591 if (strtotime($aRepMatch[2]) < $iLastNodeTimestamp) break;
592 $aPrevRepMatch = $aRepMatch;
594 if ($aPrevRepMatch) $aRepMatch = $aPrevRepMatch;
596 $sRepURL .= $aRepMatch[1].'.state.txt';
597 echo "Getting state file: $sRepURL\n";
598 $sStateFile = file_get_contents($sRepURL);
599 if (!$sStateFile || strlen($sStateFile) > 1000) fail("unable to obtain state file");
600 file_put_contents(CONST_InstallPath.'/settings/state.txt', $sStateFile);
601 echo "Updating DB status\n";
602 pg_query($oDB->connection, 'TRUNCATE import_status');
603 $sSQL = "INSERT INTO import_status VALUES('".$aRepMatch[2]."')";
604 pg_query($oDB->connection, $sSQL);
608 if (!$aCMDResult['all'])
610 fail("Cannot read state file directory.");
616 if ($aCMDResult['index'] || $aCMDResult['all'])
618 $bDidSomething = true;
620 $sBaseCmd = CONST_InstallPath.'/nominatim/nominatim -i -d '.$aDSNInfo['database'].' -P '.$aDSNInfo['port'].' -t '.$iInstances.$sOutputFile;
621 passthruCheckReturn($sBaseCmd.' -R 4');
622 if (!$aCMDResult['index-noanalyse']) pgsqlRunScript('ANALYSE');
623 passthruCheckReturn($sBaseCmd.' -r 5 -R 25');
624 if (!$aCMDResult['index-noanalyse']) pgsqlRunScript('ANALYSE');
625 passthruCheckReturn($sBaseCmd.' -r 26');
628 if ($aCMDResult['create-search-indices'] || $aCMDResult['all'])
630 echo "Search indices\n";
631 $bDidSomething = true;
633 $sSQL = 'select distinct partition from country_name';
634 $aPartitions = $oDB->getCol($sSQL);
635 if (PEAR::isError($aPartitions))
637 fail($aPartitions->getMessage());
639 if (!$aCMDResult['no-partitions']) $aPartitions[] = 0;
641 $sTemplate = file_get_contents(CONST_BasePath.'/sql/indices.src.sql');
642 $sTemplate = replace_tablespace('{ts:address-index}',
643 CONST_Tablespace_Address_Index, $sTemplate);
644 $sTemplate = replace_tablespace('{ts:search-index}',
645 CONST_Tablespace_Search_Index, $sTemplate);
646 $sTemplate = replace_tablespace('{ts:aux-index}',
647 CONST_Tablespace_Aux_Index, $sTemplate);
648 preg_match_all('#^-- start(.*?)^-- end#ms', $sTemplate, $aMatches, PREG_SET_ORDER);
649 foreach($aMatches as $aMatch)
652 foreach($aPartitions as $sPartitionName)
654 $sResult .= str_replace('-partition-', $sPartitionName, $aMatch[1]);
656 $sTemplate = str_replace($aMatch[0], $sResult, $sTemplate);
659 pgsqlRunScript($sTemplate);
662 if (isset($aCMDResult['create-website']))
664 $bDidSomething = true;
665 $sTargetDir = $aCMDResult['create-website'];
666 if (!is_dir($sTargetDir))
668 echo "You must create the website directory before calling this function.\n";
669 fail("Target directory does not exist.");
672 @symlink(CONST_InstallPath.'/website/details.php', $sTargetDir.'/details.php');
673 @symlink(CONST_InstallPath.'/website/reverse.php', $sTargetDir.'/reverse.php');
674 @symlink(CONST_InstallPath.'/website/search.php', $sTargetDir.'/search.php');
675 @symlink(CONST_InstallPath.'/website/search.php', $sTargetDir.'/index.php');
676 @symlink(CONST_InstallPath.'/website/lookup.php', $sTargetDir.'/lookup.php');
677 @symlink(CONST_InstallPath.'/website/deletable.php', $sTargetDir.'/deletable.php');
678 @symlink(CONST_InstallPath.'/website/polygons.php', $sTargetDir.'/polygons.php');
679 @symlink(CONST_InstallPath.'/website/status.php', $sTargetDir.'/status.php');
680 @symlink(CONST_BasePath.'/website/images', $sTargetDir.'/images');
681 @symlink(CONST_BasePath.'/website/js', $sTargetDir.'/js');
682 @symlink(CONST_BasePath.'/website/css', $sTargetDir.'/css');
683 echo "Symlinks created\n";
685 $sTestFile = @file_get_contents(CONST_Website_BaseURL.'js/tiles.js');
688 echo "\nWARNING: Unable to access the website at ".CONST_Website_BaseURL."\n";
689 echo "You may want to update settings/local.php with @define('CONST_Website_BaseURL', 'http://[HOST]/[PATH]/');\n";
693 if ($aCMDResult['drop'])
695 // The implementation is potentially a bit dangerous because it uses
696 // a positive selection of tables to keep, and deletes everything else.
697 // Including any tables that the unsuspecting user might have manually
698 // created. USE AT YOUR OWN PERIL.
699 $bDidSomething = true;
701 // tables we want to keep. everything else goes.
702 $aKeepTables = array(
707 "location_property*",
721 $aDropTables = array();
722 $aHaveTables = $oDB->getCol("SELECT tablename FROM pg_tables WHERE schemaname='public'");
723 if (PEAR::isError($aHaveTables))
725 fail($aPartitions->getMessage());
727 foreach($aHaveTables as $sTable)
730 foreach ($aKeepTables as $sKeep)
732 if (fnmatch($sKeep, $sTable))
738 if (!$bFound) array_push($aDropTables, $sTable);
741 foreach ($aDropTables as $sDrop)
743 if ($aCMDResult['verbose']) echo "dropping table $sDrop\n";
744 @pg_query($oDB->connection, "DROP TABLE $sDrop CASCADE");
745 // ignore warnings/errors as they might be caused by a table having
746 // been deleted already by CASCADE
749 if (!is_null(CONST_Osm2pgsql_Flatnode_File))
751 if ($aCMDResult['verbose']) echo "deleting ".CONST_Osm2pgsql_Flatnode_File."\n";
752 unlink(CONST_Osm2pgsql_Flatnode_File);
758 showUsage($aCMDOptions, true);
762 echo "Setup finished.\n";
765 function pgsqlRunScriptFile($sFilename)
767 if (!file_exists($sFilename)) fail('unable to find '.$sFilename);
769 // Convert database DSN to psql parameters
770 $aDSNInfo = DB::parseDSN(CONST_Database_DSN);
771 if (!isset($aDSNInfo['port']) || !$aDSNInfo['port']) $aDSNInfo['port'] = 5432;
772 $sCMD = 'psql -p '.$aDSNInfo['port'].' -d '.$aDSNInfo['database'];
775 if (preg_match('/\\.gz$/', $sFilename))
777 $aDescriptors = array(
778 0 => array('pipe', 'r'),
779 1 => array('pipe', 'w'),
780 2 => array('file', '/dev/null', 'a')
782 $hGzipProcess = proc_open('zcat '.$sFilename, $aDescriptors, $ahGzipPipes);
783 if (!is_resource($hGzipProcess)) fail('unable to start zcat');
784 $aReadPipe = $ahGzipPipes[1];
785 fclose($ahGzipPipes[0]);
789 $sCMD .= ' -f '.$sFilename;
790 $aReadPipe = array('pipe', 'r');
793 $aDescriptors = array(
795 1 => array('pipe', 'w'),
796 2 => array('file', '/dev/null', 'a')
799 $hProcess = proc_open($sCMD, $aDescriptors, $ahPipes);
800 if (!is_resource($hProcess)) fail('unable to start pgsql');
803 // TODO: error checking
804 while(!feof($ahPipes[1]))
806 echo fread($ahPipes[1], 4096);
810 $iReturn = proc_close($hProcess);
813 fail("pgsql returned with error code ($iReturn)");
817 fclose($ahGzipPipes[1]);
818 proc_close($hGzipProcess);
823 function pgsqlRunScript($sScript, $bfatal = true)
826 // Convert database DSN to psql parameters
827 $aDSNInfo = DB::parseDSN(CONST_Database_DSN);
828 if (!isset($aDSNInfo['port']) || !$aDSNInfo['port']) $aDSNInfo['port'] = 5432;
829 $sCMD = 'psql -p '.$aDSNInfo['port'].' -d '.$aDSNInfo['database'];
830 if ($bfatal && !$aCMDResult['ignore-errors'])
831 $sCMD .= ' -v ON_ERROR_STOP=1';
832 $aDescriptors = array(
833 0 => array('pipe', 'r'),
838 $hProcess = @proc_open($sCMD, $aDescriptors, $ahPipes);
839 if (!is_resource($hProcess)) fail('unable to start pgsql');
841 while(strlen($sScript))
843 $written = fwrite($ahPipes[0], $sScript);
844 if ($written <= 0) break;
845 $sScript = substr($sScript, $written);
848 $iReturn = proc_close($hProcess);
849 if ($bfatal && $iReturn > 0)
851 fail("pgsql returned with error code ($iReturn)");
855 function pgsqlRunRestoreData($sDumpFile)
857 // Convert database DSN to psql parameters
858 $aDSNInfo = DB::parseDSN(CONST_Database_DSN);
859 if (!isset($aDSNInfo['port']) || !$aDSNInfo['port']) $aDSNInfo['port'] = 5432;
860 $sCMD = 'pg_restore -p '.$aDSNInfo['port'].' -d '.$aDSNInfo['database'].' -Fc -a '.$sDumpFile;
862 $aDescriptors = array(
863 0 => array('pipe', 'r'),
864 1 => array('pipe', 'w'),
865 2 => array('file', '/dev/null', 'a')
868 $hProcess = proc_open($sCMD, $aDescriptors, $ahPipes);
869 if (!is_resource($hProcess)) fail('unable to start pg_restore');
873 // TODO: error checking
874 while(!feof($ahPipes[1]))
876 echo fread($ahPipes[1], 4096);
880 $iReturn = proc_close($hProcess);
883 function pgsqlRunDropAndRestore($sDumpFile)
885 // Convert database DSN to psql parameters
886 $aDSNInfo = DB::parseDSN(CONST_Database_DSN);
887 if (!isset($aDSNInfo['port']) || !$aDSNInfo['port']) $aDSNInfo['port'] = 5432;
888 $sCMD = 'pg_restore -p '.$aDSNInfo['port'].' -d '.$aDSNInfo['database'].' -Fc --clean '.$sDumpFile;
890 $aDescriptors = array(
891 0 => array('pipe', 'r'),
892 1 => array('pipe', 'w'),
893 2 => array('file', '/dev/null', 'a')
896 $hProcess = proc_open($sCMD, $aDescriptors, $ahPipes);
897 if (!is_resource($hProcess)) fail('unable to start pg_restore');
901 // TODO: error checking
902 while(!feof($ahPipes[1]))
904 echo fread($ahPipes[1], 4096);
908 $iReturn = proc_close($hProcess);
911 function passthruCheckReturn($cmd)
914 passthru($cmd, $result);
915 if ($result != 0) fail('Error executing external command: '.$cmd);
918 function replace_tablespace($sTemplate, $sTablespace, $sSql)
921 $sSql = str_replace($sTemplate, 'TABLESPACE "'.$sTablespace.'"',
924 $sSql = str_replace($sTemplate, '', $sSql);