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-minimal-tables', '', 0, 1, 0, 0, 'bool', 'Create minimal main tables'),
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('index-output', '', 0, 1, 1, 1, 'string', 'File to dump index information to'),
41 array('create-search-indices', '', 0, 1, 0, 0, 'bool', 'Create additional indices required for search and update'),
42 array('create-website', '', 0, 1, 1, 1, 'realpath', 'Create symlinks to setup web directory'),
43 array('drop', '', 0, 1, 0, 0, 'bool', 'Drop tables needed for updates, making the database readonly (EXPERIMENTAL)'),
45 getCmdOpt($_SERVER['argv'], $aCMDOptions, $aCMDResult, true, true);
47 $bDidSomething = false;
49 // Check if osm-file is set and points to a valid file if --all or --import-data is given
50 if ($aCMDResult['import-data'] || $aCMDResult['all'])
52 if (!isset($aCMDResult['osm-file']))
54 fail('missing --osm-file for data import');
57 if (!file_exists($aCMDResult['osm-file']))
59 fail('the path supplied to --osm-file does not exist');
62 if (!is_readable($aCMDResult['osm-file']))
64 fail('osm-file "'.$aCMDResult['osm-file'].'" not readable');
69 // This is a pretty hard core default - the number of processors in the box - 1
70 $iInstances = isset($aCMDResult['threads'])?$aCMDResult['threads']:(getProcessorCount()-1);
74 echo "WARNING: resetting threads to $iInstances\n";
76 if ($iInstances > getProcessorCount())
78 $iInstances = getProcessorCount();
79 echo "WARNING: resetting threads to $iInstances\n";
82 // Assume we can steal all the cache memory in the box (unless told otherwise)
83 if (isset($aCMDResult['osm2pgsql-cache']))
85 $iCacheMemory = $aCMDResult['osm2pgsql-cache'];
89 $iCacheMemory = getCacheMemoryMB();
92 $aDSNInfo = DB::parseDSN(CONST_Database_DSN);
93 if (!isset($aDSNInfo['port']) || !$aDSNInfo['port']) $aDSNInfo['port'] = 5432;
95 $fPostgisVersion = (float) CONST_Postgis_Version;
97 if ($aCMDResult['create-db'] || $aCMDResult['all'])
100 $bDidSomething = true;
101 $oDB =& DB::connect(CONST_Database_DSN, false);
102 if (!PEAR::isError($oDB))
104 fail('database already exists ('.CONST_Database_DSN.')');
106 passthruCheckReturn('createdb -E UTF-8 -p '.$aDSNInfo['port'].' '.$aDSNInfo['database']);
109 if ($aCMDResult['setup-db'] || $aCMDResult['all'])
112 $bDidSomething = true;
113 // TODO: path detection, detection memory, etc.
117 $sVersionString = $oDB->getOne('select version()');
118 preg_match('#PostgreSQL ([0-9]+)[.]([0-9]+)[^0-9]#', $sVersionString, $aMatches);
119 if (CONST_Postgresql_Version != $aMatches[1].'.'.$aMatches[2])
121 echo "ERROR: PostgreSQL version is not correct. Expected ".CONST_Postgresql_Version." found ".$aMatches[1].'.'.$aMatches[2]."\n";
125 passthru('createlang plpgsql -p '.$aDSNInfo['port'].' '.$aDSNInfo['database']);
126 $pgver = (float) CONST_Postgresql_Version;
128 pgsqlRunScriptFile(CONST_Path_Postgresql_Contrib.'/hstore.sql');
129 pgsqlRunScriptFile(CONST_BasePath.'/sql/hstore_compatability_9_0.sql');
131 pgsqlRunScript('CREATE EXTENSION hstore');
134 if ($fPostgisVersion < 2.0) {
135 pgsqlRunScriptFile(CONST_Path_Postgresql_Postgis.'/postgis.sql');
136 pgsqlRunScriptFile(CONST_Path_Postgresql_Postgis.'/spatial_ref_sys.sql');
138 pgsqlRunScript('CREATE EXTENSION IF NOT EXISTS postgis');
140 if ($fPostgisVersion < 2.1) {
141 // Function was renamed in 2.1 and throws an annoying deprecation warning
142 pgsqlRunScript('ALTER FUNCTION st_line_interpolate_point(geometry, double precision) RENAME TO ST_LineInterpolatePoint');
144 $sVersionString = $oDB->getOne('select postgis_full_version()');
145 preg_match('#POSTGIS="([0-9]+)[.]([0-9]+)[.]([0-9]+)( r([0-9]+))?"#', $sVersionString, $aMatches);
146 if (CONST_Postgis_Version != $aMatches[1].'.'.$aMatches[2])
148 echo "ERROR: PostGIS version is not correct. Expected ".CONST_Postgis_Version." found ".$aMatches[1].'.'.$aMatches[2]."\n";
152 pgsqlRunScriptFile(CONST_BasePath.'/data/country_name.sql');
153 pgsqlRunScriptFile(CONST_BasePath.'/data/country_naturalearthdata.sql');
154 pgsqlRunScriptFile(CONST_BasePath.'/data/country_osm_grid.sql');
155 pgsqlRunScriptFile(CONST_BasePath.'/data/gb_postcode_table.sql');
156 if (file_exists(CONST_BasePath.'/data/gb_postcode_data.sql.gz'))
158 pgsqlRunScriptFile(CONST_BasePath.'/data/gb_postcode_data.sql.gz');
162 echo "WARNING: external UK postcode table not found.\n";
164 pgsqlRunScriptFile(CONST_BasePath.'/data/us_statecounty.sql');
165 pgsqlRunScriptFile(CONST_BasePath.'/data/us_state.sql');
166 pgsqlRunScriptFile(CONST_BasePath.'/data/us_postcode.sql');
168 if ($aCMDResult['no-partitions'])
170 pgsqlRunScript('update country_name set partition = 0');
173 // the following will be needed by create_functions later but
174 // is only defined in the subsequently called create_tables.
175 // Create dummies here that will be overwritten by the proper
176 // versions in create-tables.
177 pgsqlRunScript('CREATE TABLE place_boundingbox ()');
178 pgsqlRunScript('create type wikipedia_article_match as ()');
181 if ($aCMDResult['import-data'] || $aCMDResult['all'])
184 $bDidSomething = true;
186 $osm2pgsql = CONST_Osm2pgsql_Binary;
187 if (!file_exists($osm2pgsql))
189 echo "Please download and build osm2pgsql.\nIf it is already installed, check the path in your local settings (settings/local.php) file.\n";
190 fail("osm2pgsql not found in '$osm2pgsql'");
193 if (!is_null(CONST_Osm2pgsql_Flatnode_File))
195 $osm2pgsql .= ' --flat-nodes '.CONST_Osm2pgsql_Flatnode_File;
197 if (CONST_Tablespace_Osm2pgsql_Data)
198 $osm2pgsql .= ' --tablespace-slim-data '.CONST_Tablespace_Osm2pgsql_Data;
199 if (CONST_Tablespace_Osm2pgsql_Index)
200 $osm2pgsql .= ' --tablespace-slim-index '.CONST_Tablespace_Osm2pgsql_Index;
201 if (CONST_Tablespace_Place_Data)
202 $osm2pgsql .= ' --tablespace-main-data '.CONST_Tablespace_Place_Data;
203 if (CONST_Tablespace_Place_Index)
204 $osm2pgsql .= ' --tablespace-main-index '.CONST_Tablespace_Place_Index;
205 $osm2pgsql .= ' -lsc -O gazetteer --hstore --number-processes 1';
206 $osm2pgsql .= ' -C '.$iCacheMemory;
207 $osm2pgsql .= ' -P '.$aDSNInfo['port'];
208 $osm2pgsql .= ' -d '.$aDSNInfo['database'].' '.$aCMDResult['osm-file'];
209 passthruCheckReturn($osm2pgsql);
212 $x = $oDB->getRow('select * from place limit 1');
213 if (PEAR::isError($x)) {
214 fail($x->getMessage());
216 if (!$x) fail('No Data');
219 if ($aCMDResult['create-functions'] || $aCMDResult['all'])
222 $bDidSomething = true;
223 if (!file_exists(CONST_InstallPath.'/module/nominatim.so')) fail("nominatim module not built");
224 $sTemplate = file_get_contents(CONST_BasePath.'/sql/functions.sql');
225 $sTemplate = str_replace('{modulepath}', CONST_InstallPath.'/module', $sTemplate);
226 if ($aCMDResult['enable-diff-updates']) $sTemplate = str_replace('RETURN NEW; -- @DIFFUPDATES@', '--', $sTemplate);
227 if ($aCMDResult['enable-debug-statements']) $sTemplate = str_replace('--DEBUG:', '', $sTemplate);
228 if (CONST_Limit_Reindexing) $sTemplate = str_replace('--LIMIT INDEXING:', '', $sTemplate);
229 pgsqlRunScript($sTemplate);
231 if ($fPostgisVersion < 2.0) {
232 echo "Helper functions for postgis < 2.0\n";
233 $sTemplate = file_get_contents(CONST_BasePath.'/sql/postgis_15_aux.sql');
235 echo "Helper functions for postgis >= 2.0\n";
236 $sTemplate = file_get_contents(CONST_BasePath.'/sql/postgis_20_aux.sql');
238 pgsqlRunScript($sTemplate);
241 if ($aCMDResult['create-minimal-tables'])
243 echo "Minimal Tables\n";
244 $bDidSomething = true;
245 pgsqlRunScriptFile(CONST_BasePath.'/sql/tables-minimal.sql');
249 // Backstop the import process - easliest possible import id
250 $sScript .= "insert into import_npi_log values (18022);\n";
252 $hFile = @fopen(CONST_BasePath.'/settings/partitionedtags.def', "r");
253 if (!$hFile) fail('unable to open list of partitions: '.CONST_BasePath.'/settings/partitionedtags.def');
255 while (($sLine = fgets($hFile, 4096)) !== false && $sLine && substr($sLine,0,1) !='#')
257 list($sClass, $sType) = explode(' ', trim($sLine));
258 $sScript .= "create table place_classtype_".$sClass."_".$sType." as ";
259 $sScript .= "select place_id as place_id,geometry as centroid from placex limit 0;\n";
261 $sScript .= "CREATE INDEX idx_place_classtype_".$sClass."_".$sType."_centroid ";
262 $sScript .= "ON place_classtype_".$sClass."_".$sType." USING GIST (centroid);\n";
264 $sScript .= "CREATE INDEX idx_place_classtype_".$sClass."_".$sType."_place_id ";
265 $sScript .= "ON place_classtype_".$sClass."_".$sType." USING btree(place_id);\n";
268 pgsqlRunScript($sScript);
271 if ($aCMDResult['create-tables'] || $aCMDResult['all'])
273 $bDidSomething = true;
276 $sTemplate = file_get_contents(CONST_BasePath.'/sql/tables.sql');
277 $sTemplate = str_replace('{www-user}', CONST_Database_Web_User, $sTemplate);
278 $sTemplate = replace_tablespace('{ts:address-data}',
279 CONST_Tablespace_Address_Data, $sTemplate);
280 $sTemplate = replace_tablespace('{ts:address-index}',
281 CONST_Tablespace_Address_Index, $sTemplate);
282 $sTemplate = replace_tablespace('{ts:search-data}',
283 CONST_Tablespace_Search_Data, $sTemplate);
284 $sTemplate = replace_tablespace('{ts:search-index}',
285 CONST_Tablespace_Search_Index, $sTemplate);
286 $sTemplate = replace_tablespace('{ts:aux-data}',
287 CONST_Tablespace_Aux_Data, $sTemplate);
288 $sTemplate = replace_tablespace('{ts:aux-index}',
289 CONST_Tablespace_Aux_Index, $sTemplate);
290 pgsqlRunScript($sTemplate, false);
292 // re-run the functions
294 $sTemplate = file_get_contents(CONST_BasePath.'/sql/functions.sql');
295 $sTemplate = str_replace('{modulepath}',
296 CONST_InstallPath.'/module', $sTemplate);
297 pgsqlRunScript($sTemplate);
300 if ($aCMDResult['create-partition-tables'] || $aCMDResult['all'])
302 echo "Partition Tables\n";
303 $bDidSomething = true;
305 $sSQL = 'select distinct partition from country_name';
306 $aPartitions = $oDB->getCol($sSQL);
307 if (PEAR::isError($aPartitions))
309 fail($aPartitions->getMessage());
311 if (!$aCMDResult['no-partitions']) $aPartitions[] = 0;
313 $sTemplate = file_get_contents(CONST_BasePath.'/sql/partition-tables.src.sql');
314 $sTemplate = replace_tablespace('{ts:address-data}',
315 CONST_Tablespace_Address_Data, $sTemplate);
316 $sTemplate = replace_tablespace('{ts:address-index}',
317 CONST_Tablespace_Address_Index, $sTemplate);
318 $sTemplate = replace_tablespace('{ts:search-data}',
319 CONST_Tablespace_Search_Data, $sTemplate);
320 $sTemplate = replace_tablespace('{ts:search-index}',
321 CONST_Tablespace_Search_Index, $sTemplate);
322 $sTemplate = replace_tablespace('{ts:aux-data}',
323 CONST_Tablespace_Aux_Data, $sTemplate);
324 $sTemplate = replace_tablespace('{ts:aux-index}',
325 CONST_Tablespace_Aux_Index, $sTemplate);
326 preg_match_all('#^-- start(.*?)^-- end#ms', $sTemplate, $aMatches, PREG_SET_ORDER);
327 foreach($aMatches as $aMatch)
330 foreach($aPartitions as $sPartitionName)
332 $sResult .= str_replace('-partition-', $sPartitionName, $aMatch[1]);
334 $sTemplate = str_replace($aMatch[0], $sResult, $sTemplate);
337 pgsqlRunScript($sTemplate);
341 if ($aCMDResult['create-partition-functions'] || $aCMDResult['all'])
343 echo "Partition Functions\n";
344 $bDidSomething = true;
346 $sSQL = 'select distinct partition from country_name';
347 $aPartitions = $oDB->getCol($sSQL);
348 if (PEAR::isError($aPartitions))
350 fail($aPartitions->getMessage());
352 if (!$aCMDResult['no-partitions']) $aPartitions[] = 0;
354 $sTemplate = file_get_contents(CONST_BasePath.'/sql/partition-functions.src.sql');
355 preg_match_all('#^-- start(.*?)^-- end#ms', $sTemplate, $aMatches, PREG_SET_ORDER);
356 foreach($aMatches as $aMatch)
359 foreach($aPartitions as $sPartitionName)
361 $sResult .= str_replace('-partition-', $sPartitionName, $aMatch[1]);
363 $sTemplate = str_replace($aMatch[0], $sResult, $sTemplate);
366 pgsqlRunScript($sTemplate);
369 if ($aCMDResult['import-wikipedia-articles'] || $aCMDResult['all'])
371 $bDidSomething = true;
372 $sWikiArticlesFile = CONST_BasePath.'/data/wikipedia_article.sql.bin';
373 $sWikiRedirectsFile = CONST_BasePath.'/data/wikipedia_redirect.sql.bin';
374 if (file_exists($sWikiArticlesFile))
376 echo "Importing wikipedia articles...";
377 pgsqlRunDropAndRestore($sWikiArticlesFile);
382 echo "WARNING: wikipedia article dump file not found - places will have default importance\n";
384 if (file_exists($sWikiRedirectsFile))
386 echo "Importing wikipedia redirects...";
387 pgsqlRunDropAndRestore($sWikiRedirectsFile);
392 echo "WARNING: wikipedia redirect dump file not found - some place importance values may be missing\n";
397 if ($aCMDResult['load-data'] || $aCMDResult['all'])
399 echo "Drop old Data\n";
400 $bDidSomething = true;
403 if (!pg_query($oDB->connection, 'TRUNCATE word')) fail(pg_last_error($oDB->connection));
405 if (!pg_query($oDB->connection, 'TRUNCATE placex')) fail(pg_last_error($oDB->connection));
407 if (!pg_query($oDB->connection, 'TRUNCATE place_addressline')) fail(pg_last_error($oDB->connection));
409 if (!pg_query($oDB->connection, 'TRUNCATE place_boundingbox')) fail(pg_last_error($oDB->connection));
411 if (!pg_query($oDB->connection, 'TRUNCATE location_area')) fail(pg_last_error($oDB->connection));
413 if (!pg_query($oDB->connection, 'TRUNCATE search_name')) fail(pg_last_error($oDB->connection));
415 if (!pg_query($oDB->connection, 'TRUNCATE search_name_blank')) fail(pg_last_error($oDB->connection));
417 if (!pg_query($oDB->connection, 'DROP SEQUENCE seq_place')) fail(pg_last_error($oDB->connection));
419 if (!pg_query($oDB->connection, 'CREATE SEQUENCE seq_place start 100000')) fail(pg_last_error($oDB->connection));
422 $sSQL = 'select distinct partition from country_name';
423 $aPartitions = $oDB->getCol($sSQL);
424 if (PEAR::isError($aPartitions))
426 fail($aPartitions->getMessage());
428 if (!$aCMDResult['no-partitions']) $aPartitions[] = 0;
429 foreach($aPartitions as $sPartition)
431 if (!pg_query($oDB->connection, 'TRUNCATE location_road_'.$sPartition)) fail(pg_last_error($oDB->connection));
435 // used by getorcreate_word_id to ignore frequent partial words
436 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));
439 // pre-create the word list
440 if (!$aCMDResult['disable-token-precalc'])
442 echo "Loading word list\n";
443 pgsqlRunScriptFile(CONST_BasePath.'/data/words.sql');
447 $aDBInstances = array();
448 for($i = 0; $i < $iInstances; $i++)
450 $aDBInstances[$i] =& getDB(true);
451 $sSQL = 'insert into placex (osm_type, osm_id, class, type, name, admin_level, ';
452 $sSQL .= 'housenumber, street, addr_place, isin, postcode, country_code, extratags, ';
453 $sSQL .= 'geometry) select * from place where osm_id % '.$iInstances.' = '.$i;
454 if ($aCMDResult['verbose']) echo "$sSQL\n";
455 if (!pg_send_query($aDBInstances[$i]->connection, $sSQL)) fail(pg_last_error($oDB->connection));
461 for($i = 0; $i < $iInstances; $i++)
463 if (pg_connection_busy($aDBInstances[$i]->connection)) $bAnyBusy = true;
469 echo "Reanalysing database...\n";
470 pgsqlRunScript('ANALYSE');
473 if ($aCMDResult['import-tiger-data'])
475 $bDidSomething = true;
477 $sTemplate = file_get_contents(CONST_BasePath.'/sql/tiger_import_start.sql');
478 $sTemplate = str_replace('{www-user}', CONST_Database_Web_User, $sTemplate);
479 $sTemplate = replace_tablespace('{ts:aux-data}',
480 CONST_Tablespace_Aux_Data, $sTemplate);
481 $sTemplate = replace_tablespace('{ts:aux-index}',
482 CONST_Tablespace_Aux_Index, $sTemplate);
483 pgsqlRunScript($sTemplate, false);
485 $aDBInstances = array();
486 for($i = 0; $i < $iInstances; $i++)
488 $aDBInstances[$i] =& getDB(true);
491 foreach(glob(CONST_Tiger_Data_Path.'/*.sql') as $sFile)
494 $hFile = fopen($sFile, "r");
495 $sSQL = fgets($hFile, 100000);
500 for($i = 0; $i < $iInstances; $i++)
502 if (!pg_connection_busy($aDBInstances[$i]->connection))
504 while(pg_get_result($aDBInstances[$i]->connection));
505 $sSQL = fgets($hFile, 100000);
507 if (!pg_send_query($aDBInstances[$i]->connection, $sSQL)) fail(pg_last_error($oDB->connection));
525 for($i = 0; $i < $iInstances; $i++)
527 if (pg_connection_busy($aDBInstances[$i]->connection)) $bAnyBusy = true;
534 echo "Creating indexes\n";
535 $sTemplate = file_get_contents(CONST_BasePath.'/sql/tiger_import_finish.sql');
536 $sTemplate = str_replace('{www-user}', CONST_Database_Web_User, $sTemplate);
537 $sTemplate = replace_tablespace('{ts:aux-data}',
538 CONST_Tablespace_Aux_Data, $sTemplate);
539 $sTemplate = replace_tablespace('{ts:aux-index}',
540 CONST_Tablespace_Aux_Index, $sTemplate);
541 pgsqlRunScript($sTemplate, false);
544 if ($aCMDResult['calculate-postcodes'] || $aCMDResult['all'])
546 $bDidSomething = true;
548 if (!pg_query($oDB->connection, 'DELETE from placex where osm_type=\'P\'')) fail(pg_last_error($oDB->connection));
549 $sSQL = "insert into placex (osm_type,osm_id,class,type,postcode,calculated_country_code,geometry) ";
550 $sSQL .= "select 'P',nextval('seq_postcodes'),'place','postcode',postcode,calculated_country_code,";
551 $sSQL .= "ST_SetSRID(ST_Point(x,y),4326) as geometry from (select calculated_country_code,postcode,";
552 $sSQL .= "avg(st_x(st_centroid(geometry))) as x,avg(st_y(st_centroid(geometry))) as y ";
553 $sSQL .= "from placex where postcode is not null group by calculated_country_code,postcode) as x";
554 if (!pg_query($oDB->connection, $sSQL)) fail(pg_last_error($oDB->connection));
556 $sSQL = "insert into placex (osm_type,osm_id,class,type,postcode,calculated_country_code,geometry) ";
557 $sSQL .= "select 'P',nextval('seq_postcodes'),'place','postcode',postcode,'us',";
558 $sSQL .= "ST_SetSRID(ST_Point(x,y),4326) as geometry from us_postcode";
559 if (!pg_query($oDB->connection, $sSQL)) fail(pg_last_error($oDB->connection));
562 if ($aCMDResult['osmosis-init'] || ($aCMDResult['all'] && !$aCMDResult['drop'])) // no use doing osmosis-init when dropping update tables
564 $bDidSomething = true;
567 if (!file_exists(CONST_Osmosis_Binary))
569 echo "Please download osmosis.\nIf it is already installed, check the path in your local settings (settings/local.php) file.\n";
570 if (!$aCMDResult['all'])
572 fail("osmosis not found in '".CONST_Osmosis_Binary."'");
577 if (file_exists(CONST_InstallPath.'/settings/configuration.txt'))
579 echo "settings/configuration.txt already exists\n";
583 passthru(CONST_Osmosis_Binary.' --read-replication-interval-init '.CONST_InstallPath.'/settings');
584 // update osmosis configuration.txt with our settings
585 passthru("sed -i 's!baseUrl=.*!baseUrl=".CONST_Replication_Url."!' ".CONST_InstallPath.'/settings/configuration.txt');
586 passthru("sed -i 's:maxInterval = .*:maxInterval = ".CONST_Replication_MaxInterval.":' ".CONST_InstallPath.'/settings/configuration.txt');
589 // Find the last node in the DB
590 $iLastOSMID = $oDB->getOne("select max(osm_id) from place where osm_type = 'N'");
592 // Lookup the timestamp that node was created (less 3 hours for margin for changsets to be closed)
593 $sLastNodeURL = 'http://www.openstreetmap.org/api/0.6/node/'.$iLastOSMID."/1";
594 $sLastNodeXML = file_get_contents($sLastNodeURL);
595 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);
596 $iLastNodeTimestamp = strtotime($aLastNodeDate[1]) - (3*60*60);
598 // Search for the correct state file - uses file timestamps so need to sort by date descending
599 $sRepURL = CONST_Replication_Url."/";
600 $sRep = file_get_contents($sRepURL."?C=M;O=D;F=1");
601 // download.geofabrik.de: <a href="000/">000/</a></td><td align="right">26-Feb-2013 11:53 </td>
602 // planet.openstreetmap.org: <a href="273/">273/</a> 2013-03-11 07:41 -
603 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);
606 $aPrevRepMatch = false;
607 foreach($aRepMatches as $aRepMatch)
609 if (strtotime($aRepMatch[2]) < $iLastNodeTimestamp) break;
610 $aPrevRepMatch = $aRepMatch;
612 if ($aPrevRepMatch) $aRepMatch = $aPrevRepMatch;
614 $sRepURL .= $aRepMatch[1];
615 $sRep = file_get_contents($sRepURL."?C=M;O=D;F=1");
616 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);
617 $aPrevRepMatch = false;
618 foreach($aRepMatches as $aRepMatch)
620 if (strtotime($aRepMatch[2]) < $iLastNodeTimestamp) break;
621 $aPrevRepMatch = $aRepMatch;
623 if ($aPrevRepMatch) $aRepMatch = $aPrevRepMatch;
625 $sRepURL .= $aRepMatch[1];
626 $sRep = file_get_contents($sRepURL."?C=M;O=D;F=1");
627 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);
628 $aPrevRepMatch = false;
629 foreach($aRepMatches as $aRepMatch)
631 if (strtotime($aRepMatch[2]) < $iLastNodeTimestamp) break;
632 $aPrevRepMatch = $aRepMatch;
634 if ($aPrevRepMatch) $aRepMatch = $aPrevRepMatch;
636 $sRepURL .= $aRepMatch[1].'.state.txt';
637 echo "Getting state file: $sRepURL\n";
638 $sStateFile = file_get_contents($sRepURL);
639 if (!$sStateFile || strlen($sStateFile) > 1000) fail("unable to obtain state file");
640 file_put_contents(CONST_InstallPath.'/settings/state.txt', $sStateFile);
641 echo "Updating DB status\n";
642 pg_query($oDB->connection, 'TRUNCATE import_status');
643 $sSQL = "INSERT INTO import_status VALUES('".$aRepMatch[2]."')";
644 pg_query($oDB->connection, $sSQL);
648 if (!$aCMDResult['all'])
650 fail("Cannot read state file directory.");
656 if ($aCMDResult['index'] || $aCMDResult['all'])
658 $bDidSomething = true;
660 if (isset($aCMDResult['index-output'])) $sOutputFile = ' -F '.$aCMDResult['index-output'];
661 $sBaseCmd = CONST_InstallPath.'/nominatim/nominatim -i -d '.$aDSNInfo['database'].' -P '.$aDSNInfo['port'].' -t '.$iInstances.$sOutputFile;
662 passthruCheckReturn($sBaseCmd.' -R 4');
663 if (!$aCMDResult['index-noanalyse']) pgsqlRunScript('ANALYSE');
664 passthruCheckReturn($sBaseCmd.' -r 5 -R 25');
665 if (!$aCMDResult['index-noanalyse']) pgsqlRunScript('ANALYSE');
666 passthruCheckReturn($sBaseCmd.' -r 26');
669 if ($aCMDResult['create-search-indices'] || $aCMDResult['all'])
671 echo "Search indices\n";
672 $bDidSomething = true;
674 $sSQL = 'select distinct partition from country_name';
675 $aPartitions = $oDB->getCol($sSQL);
676 if (PEAR::isError($aPartitions))
678 fail($aPartitions->getMessage());
680 if (!$aCMDResult['no-partitions']) $aPartitions[] = 0;
682 $sTemplate = file_get_contents(CONST_BasePath.'/sql/indices.src.sql');
683 $sTemplate = replace_tablespace('{ts:address-index}',
684 CONST_Tablespace_Address_Index, $sTemplate);
685 $sTemplate = replace_tablespace('{ts:search-index}',
686 CONST_Tablespace_Search_Index, $sTemplate);
687 $sTemplate = replace_tablespace('{ts:aux-index}',
688 CONST_Tablespace_Aux_Index, $sTemplate);
689 preg_match_all('#^-- start(.*?)^-- end#ms', $sTemplate, $aMatches, PREG_SET_ORDER);
690 foreach($aMatches as $aMatch)
693 foreach($aPartitions as $sPartitionName)
695 $sResult .= str_replace('-partition-', $sPartitionName, $aMatch[1]);
697 $sTemplate = str_replace($aMatch[0], $sResult, $sTemplate);
700 pgsqlRunScript($sTemplate);
703 if (isset($aCMDResult['create-website']))
705 $bDidSomething = true;
706 $sTargetDir = $aCMDResult['create-website'];
707 if (!is_dir($sTargetDir))
709 echo "You must create the website directory before calling this function.\n";
710 fail("Target directory does not exist.");
713 @symlink(CONST_BasePath.'/website/details.php', $sTargetDir.'/details.php');
714 @symlink(CONST_BasePath.'/website/reverse.php', $sTargetDir.'/reverse.php');
715 @symlink(CONST_BasePath.'/website/search.php', $sTargetDir.'/search.php');
716 @symlink(CONST_BasePath.'/website/search.php', $sTargetDir.'/index.php');
717 @symlink(CONST_BasePath.'/website/lookup.php', $sTargetDir.'/lookup.php');
718 @symlink(CONST_BasePath.'/website/deletable.php', $sTargetDir.'/deletable.php');
719 @symlink(CONST_BasePath.'/website/polygons.php', $sTargetDir.'/polygons.php');
720 @symlink(CONST_BasePath.'/website/status.php', $sTargetDir.'/status.php');
721 @symlink(CONST_BasePath.'/website/images', $sTargetDir.'/images');
722 @symlink(CONST_BasePath.'/website/js', $sTargetDir.'/js');
723 @symlink(CONST_BasePath.'/website/css', $sTargetDir.'/css');
724 echo "Symlinks created\n";
726 $sTestFile = @file_get_contents(CONST_Website_BaseURL.'js/tiles.js');
729 echo "\nWARNING: Unable to access the website at ".CONST_Website_BaseURL."\n";
730 echo "You may want to update settings/local.php with @define('CONST_Website_BaseURL', 'http://[HOST]/[PATH]/');\n";
734 if ($aCMDResult['drop'])
736 // The implementation is potentially a bit dangerous because it uses
737 // a positive selection of tables to keep, and deletes everything else.
738 // Including any tables that the unsuspecting user might have manually
739 // created. USE AT YOUR OWN PERIL.
740 $bDidSomething = true;
742 // tables we want to keep. everything else goes.
743 $aKeepTables = array(
748 "location_property*",
762 $aDropTables = array();
763 $aHaveTables = $oDB->getCol("SELECT tablename FROM pg_tables WHERE schemaname='public'");
764 if (PEAR::isError($aHaveTables))
766 fail($aPartitions->getMessage());
768 foreach($aHaveTables as $sTable)
771 foreach ($aKeepTables as $sKeep)
773 if (fnmatch($sKeep, $sTable))
779 if (!$bFound) array_push($aDropTables, $sTable);
782 foreach ($aDropTables as $sDrop)
784 if ($aCMDResult['verbose']) echo "dropping table $sDrop\n";
785 @pg_query($oDB->connection, "DROP TABLE $sDrop CASCADE");
786 // ignore warnings/errors as they might be caused by a table having
787 // been deleted already by CASCADE
790 if (!is_null(CONST_Osm2pgsql_Flatnode_File))
792 if ($aCMDResult['verbose']) echo "deleting ".CONST_Osm2pgsql_Flatnode_File."\n";
793 unlink(CONST_Osm2pgsql_Flatnode_File);
799 showUsage($aCMDOptions, true);
803 echo "Setup finished.\n";
806 function pgsqlRunScriptFile($sFilename)
808 if (!file_exists($sFilename)) fail('unable to find '.$sFilename);
810 // Convert database DSN to psql parameters
811 $aDSNInfo = DB::parseDSN(CONST_Database_DSN);
812 if (!isset($aDSNInfo['port']) || !$aDSNInfo['port']) $aDSNInfo['port'] = 5432;
813 $sCMD = 'psql -p '.$aDSNInfo['port'].' -d '.$aDSNInfo['database'];
816 if (preg_match('/\\.gz$/', $sFilename))
818 $aDescriptors = array(
819 0 => array('pipe', 'r'),
820 1 => array('pipe', 'w'),
821 2 => array('file', '/dev/null', 'a')
823 $hGzipProcess = proc_open('zcat '.$sFilename, $aDescriptors, $ahGzipPipes);
824 if (!is_resource($hGzipProcess)) fail('unable to start zcat');
825 $aReadPipe = $ahGzipPipes[1];
826 fclose($ahGzipPipes[0]);
830 $sCMD .= ' -f '.$sFilename;
831 $aReadPipe = array('pipe', 'r');
834 $aDescriptors = array(
836 1 => array('pipe', 'w'),
837 2 => array('file', '/dev/null', 'a')
840 $hProcess = proc_open($sCMD, $aDescriptors, $ahPipes);
841 if (!is_resource($hProcess)) fail('unable to start pgsql');
844 // TODO: error checking
845 while(!feof($ahPipes[1]))
847 echo fread($ahPipes[1], 4096);
851 $iReturn = proc_close($hProcess);
854 fail("pgsql returned with error code ($iReturn)");
858 fclose($ahGzipPipes[1]);
859 proc_close($hGzipProcess);
864 function pgsqlRunScript($sScript, $bfatal = true)
867 // Convert database DSN to psql parameters
868 $aDSNInfo = DB::parseDSN(CONST_Database_DSN);
869 if (!isset($aDSNInfo['port']) || !$aDSNInfo['port']) $aDSNInfo['port'] = 5432;
870 $sCMD = 'psql -p '.$aDSNInfo['port'].' -d '.$aDSNInfo['database'];
871 if ($bfatal && !$aCMDResult['ignore-errors'])
872 $sCMD .= ' -v ON_ERROR_STOP=1';
873 $aDescriptors = array(
874 0 => array('pipe', 'r'),
879 $hProcess = @proc_open($sCMD, $aDescriptors, $ahPipes);
880 if (!is_resource($hProcess)) fail('unable to start pgsql');
882 while(strlen($sScript))
884 $written = fwrite($ahPipes[0], $sScript);
885 if ($written <= 0) break;
886 $sScript = substr($sScript, $written);
889 $iReturn = proc_close($hProcess);
890 if ($bfatal && $iReturn > 0)
892 fail("pgsql returned with error code ($iReturn)");
896 function pgsqlRunRestoreData($sDumpFile)
898 // Convert database DSN to psql parameters
899 $aDSNInfo = DB::parseDSN(CONST_Database_DSN);
900 if (!isset($aDSNInfo['port']) || !$aDSNInfo['port']) $aDSNInfo['port'] = 5432;
901 $sCMD = 'pg_restore -p '.$aDSNInfo['port'].' -d '.$aDSNInfo['database'].' -Fc -a '.$sDumpFile;
903 $aDescriptors = array(
904 0 => array('pipe', 'r'),
905 1 => array('pipe', 'w'),
906 2 => array('file', '/dev/null', 'a')
909 $hProcess = proc_open($sCMD, $aDescriptors, $ahPipes);
910 if (!is_resource($hProcess)) fail('unable to start pg_restore');
914 // TODO: error checking
915 while(!feof($ahPipes[1]))
917 echo fread($ahPipes[1], 4096);
921 $iReturn = proc_close($hProcess);
924 function pgsqlRunDropAndRestore($sDumpFile)
926 // Convert database DSN to psql parameters
927 $aDSNInfo = DB::parseDSN(CONST_Database_DSN);
928 if (!isset($aDSNInfo['port']) || !$aDSNInfo['port']) $aDSNInfo['port'] = 5432;
929 $sCMD = 'pg_restore -p '.$aDSNInfo['port'].' -d '.$aDSNInfo['database'].' -Fc --clean '.$sDumpFile;
931 $aDescriptors = array(
932 0 => array('pipe', 'r'),
933 1 => array('pipe', 'w'),
934 2 => array('file', '/dev/null', 'a')
937 $hProcess = proc_open($sCMD, $aDescriptors, $ahPipes);
938 if (!is_resource($hProcess)) fail('unable to start pg_restore');
942 // TODO: error checking
943 while(!feof($ahPipes[1]))
945 echo fread($ahPipes[1], 4096);
949 $iReturn = proc_close($hProcess);
952 function passthruCheckReturn($cmd)
955 passthru($cmd, $result);
956 if ($result != 0) fail('Error executing external command: '.$cmd);
959 function replace_tablespace($sTemplate, $sTablespace, $sSql)
962 $sSql = str_replace($sTemplate, 'TABLESPACE "'.$sTablespace.'"',
965 $sSql = str_replace($sTemplate, '', $sSql);