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 location_property_osmline')) fail(pg_last_error($oDB->connection));
409 if (!pg_query($oDB->connection, 'TRUNCATE place_addressline')) fail(pg_last_error($oDB->connection));
411 if (!pg_query($oDB->connection, 'TRUNCATE place_boundingbox')) fail(pg_last_error($oDB->connection));
413 if (!pg_query($oDB->connection, 'TRUNCATE location_area')) fail(pg_last_error($oDB->connection));
415 if (!pg_query($oDB->connection, 'TRUNCATE search_name')) fail(pg_last_error($oDB->connection));
417 if (!pg_query($oDB->connection, 'TRUNCATE search_name_blank')) fail(pg_last_error($oDB->connection));
419 if (!pg_query($oDB->connection, 'DROP SEQUENCE seq_place')) fail(pg_last_error($oDB->connection));
421 if (!pg_query($oDB->connection, 'CREATE SEQUENCE seq_place start 100000')) fail(pg_last_error($oDB->connection));
424 $sSQL = 'select distinct partition from country_name';
425 $aPartitions = $oDB->getCol($sSQL);
426 if (PEAR::isError($aPartitions))
428 fail($aPartitions->getMessage());
430 if (!$aCMDResult['no-partitions']) $aPartitions[] = 0;
431 foreach($aPartitions as $sPartition)
433 if (!pg_query($oDB->connection, 'TRUNCATE location_road_'.$sPartition)) fail(pg_last_error($oDB->connection));
437 // used by getorcreate_word_id to ignore frequent partial words
438 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));
441 // pre-create the word list
442 if (!$aCMDResult['disable-token-precalc'])
444 echo "Loading word list\n";
445 pgsqlRunScriptFile(CONST_BasePath.'/data/words.sql');
449 $aDBInstances = array();
451 $aQueriesPlacex = array();
452 $aQueriesOsmline = array();
453 // the query is divided into parcels, so that the work between the processes, i.e. the DBInstances, will be evenly distributed
454 $iNumberOfParcels = 100;
455 for($i = 0; $i < $iNumberOfParcels; $i++)
457 $sSQL = 'insert into placex (osm_type, osm_id, class, type, name, admin_level, ';
458 $sSQL .= 'housenumber, street, addr_place, isin, postcode, country_code, extratags, ';
459 $sSQL .= 'geometry) select * from place where osm_id % '.$iNumberOfParcels.' = '.$i.' and not ';
460 $sSQL .= '(class=\'place\' and type=\'houses\' and osm_type=\'W\' and ST_GeometryType(geometry) = \'ST_LineString\');';
461 array_push($aQueriesPlacex, $sSQL);
462 $sSQL = 'select insert_osmline (osm_id, housenumber, street, addr_place, postcode, country_code, ';
463 $sSQL .= 'geometry) from place where osm_id % '.$iNumberOfParcels.' = '.$i.' and ';
464 $sSQL .= 'class=\'place\' and type=\'houses\' and osm_type=\'W\' and ST_GeometryType(geometry) = \'ST_LineString\'';
465 array_push($aQueriesOsmline, $sSQL);
468 for($i = 0; $i < $iInstances; $i++)
470 $aDBInstances[$i] =& getDB(true);
472 // now execute the query blocks, in the first round for placex, then for osmline,
473 // because insert_osmline depends on the placex table
474 echo 'Inserting from place to placex.';
475 $aQueries = $aQueriesPlacex;
476 for($j = 0; $j < 2; $j++)
483 for($i = 0; $i < $iInstances; $i++)
485 if (pg_connection_busy($aDBInstances[$i]->connection))
489 else if (count($aQueries) > 0)
491 $query = array_pop($aQueries);
492 if (!pg_send_query($aDBInstances[$i]->connection, $query))
494 fail(pg_last_error($oDB->connection));
498 pg_get_result($aDBInstances[$i]->connection);
507 if ($j == 0) //for the second round with osmline
509 echo 'Inserting from place to osmline.';
510 $aQueries = $aQueriesOsmline;
514 echo "Reanalysing database...\n";
515 pgsqlRunScript('ANALYSE');
518 if ($aCMDResult['import-tiger-data'])
520 $bDidSomething = true;
522 $sTemplate = file_get_contents(CONST_BasePath.'/sql/tiger_import_start.sql');
523 $sTemplate = str_replace('{www-user}', CONST_Database_Web_User, $sTemplate);
524 $sTemplate = replace_tablespace('{ts:aux-data}',
525 CONST_Tablespace_Aux_Data, $sTemplate);
526 $sTemplate = replace_tablespace('{ts:aux-index}',
527 CONST_Tablespace_Aux_Index, $sTemplate);
528 pgsqlRunScript($sTemplate, false);
530 $aDBInstances = array();
531 for($i = 0; $i < $iInstances; $i++)
533 $aDBInstances[$i] =& getDB(true);
536 foreach(glob(CONST_Tiger_Data_Path.'/*.sql') as $sFile)
539 $hFile = fopen($sFile, "r");
540 $sSQL = fgets($hFile, 100000);
545 for($i = 0; $i < $iInstances; $i++)
547 if (!pg_connection_busy($aDBInstances[$i]->connection))
549 while(pg_get_result($aDBInstances[$i]->connection));
550 $sSQL = fgets($hFile, 100000);
552 if (!pg_send_query($aDBInstances[$i]->connection, $sSQL)) fail(pg_last_error($oDB->connection));
570 for($i = 0; $i < $iInstances; $i++)
572 if (pg_connection_busy($aDBInstances[$i]->connection)) $bAnyBusy = true;
579 echo "Creating indexes\n";
580 $sTemplate = file_get_contents(CONST_BasePath.'/sql/tiger_import_finish.sql');
581 $sTemplate = str_replace('{www-user}', CONST_Database_Web_User, $sTemplate);
582 $sTemplate = replace_tablespace('{ts:aux-data}',
583 CONST_Tablespace_Aux_Data, $sTemplate);
584 $sTemplate = replace_tablespace('{ts:aux-index}',
585 CONST_Tablespace_Aux_Index, $sTemplate);
586 pgsqlRunScript($sTemplate, false);
589 if ($aCMDResult['calculate-postcodes'] || $aCMDResult['all'])
591 $bDidSomething = true;
593 if (!pg_query($oDB->connection, 'DELETE from placex where osm_type=\'P\'')) fail(pg_last_error($oDB->connection));
594 $sSQL = "insert into placex (osm_type,osm_id,class,type,postcode,calculated_country_code,geometry) ";
595 $sSQL .= "select 'P',nextval('seq_postcodes'),'place','postcode',postcode,calculated_country_code,";
596 $sSQL .= "ST_SetSRID(ST_Point(x,y),4326) as geometry from (select calculated_country_code,postcode,";
597 $sSQL .= "avg(st_x(st_centroid(geometry))) as x,avg(st_y(st_centroid(geometry))) as y ";
598 $sSQL .= "from placex where postcode is not null group by calculated_country_code,postcode) as x";
599 if (!pg_query($oDB->connection, $sSQL)) fail(pg_last_error($oDB->connection));
600 $sSQL = "insert into placex (osm_type,osm_id,class,type,postcode,calculated_country_code,geometry) ";
601 $sSQL .= "select 'P',nextval('seq_postcodes'),'place','postcode',postcode,'us',";
602 $sSQL .= "ST_SetSRID(ST_Point(x,y),4326) as geometry from us_postcode";
603 if (!pg_query($oDB->connection, $sSQL)) fail(pg_last_error($oDB->connection));
606 if ($aCMDResult['osmosis-init'] || ($aCMDResult['all'] && !$aCMDResult['drop'])) // no use doing osmosis-init when dropping update tables
608 $bDidSomething = true;
611 if (!file_exists(CONST_Osmosis_Binary))
613 echo "Please download osmosis.\nIf it is already installed, check the path in your local settings (settings/local.php) file.\n";
614 if (!$aCMDResult['all'])
616 fail("osmosis not found in '".CONST_Osmosis_Binary."'");
621 if (file_exists(CONST_InstallPath.'/settings/configuration.txt'))
623 echo "settings/configuration.txt already exists\n";
627 passthru(CONST_Osmosis_Binary.' --read-replication-interval-init '.CONST_InstallPath.'/settings');
628 // update osmosis configuration.txt with our settings
629 passthru("sed -i 's!baseUrl=.*!baseUrl=".CONST_Replication_Url."!' ".CONST_InstallPath.'/settings/configuration.txt');
630 passthru("sed -i 's:maxInterval = .*:maxInterval = ".CONST_Replication_MaxInterval.":' ".CONST_InstallPath.'/settings/configuration.txt');
633 // Find the last node in the DB
634 $iLastOSMID = $oDB->getOne("select max(osm_id) from place where osm_type = 'N'");
636 // Lookup the timestamp that node was created (less 3 hours for margin for changsets to be closed)
637 $sLastNodeURL = 'http://www.openstreetmap.org/api/0.6/node/'.$iLastOSMID."/1";
638 $sLastNodeXML = file_get_contents($sLastNodeURL);
639 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);
640 $iLastNodeTimestamp = strtotime($aLastNodeDate[1]) - (3*60*60);
642 // Search for the correct state file - uses file timestamps so need to sort by date descending
643 $sRepURL = CONST_Replication_Url."/";
644 $sRep = file_get_contents($sRepURL."?C=M;O=D;F=1");
645 // download.geofabrik.de: <a href="000/">000/</a></td><td align="right">26-Feb-2013 11:53 </td>
646 // planet.openstreetmap.org: <a href="273/">273/</a> 2013-03-11 07:41 -
647 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);
650 $aPrevRepMatch = false;
651 foreach($aRepMatches as $aRepMatch)
653 if (strtotime($aRepMatch[2]) < $iLastNodeTimestamp) break;
654 $aPrevRepMatch = $aRepMatch;
656 if ($aPrevRepMatch) $aRepMatch = $aPrevRepMatch;
658 $sRepURL .= $aRepMatch[1];
659 $sRep = file_get_contents($sRepURL."?C=M;O=D;F=1");
660 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);
661 $aPrevRepMatch = false;
662 foreach($aRepMatches as $aRepMatch)
664 if (strtotime($aRepMatch[2]) < $iLastNodeTimestamp) break;
665 $aPrevRepMatch = $aRepMatch;
667 if ($aPrevRepMatch) $aRepMatch = $aPrevRepMatch;
669 $sRepURL .= $aRepMatch[1];
670 $sRep = file_get_contents($sRepURL."?C=M;O=D;F=1");
671 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);
672 $aPrevRepMatch = false;
673 foreach($aRepMatches as $aRepMatch)
675 if (strtotime($aRepMatch[2]) < $iLastNodeTimestamp) break;
676 $aPrevRepMatch = $aRepMatch;
678 if ($aPrevRepMatch) $aRepMatch = $aPrevRepMatch;
680 $sRepURL .= $aRepMatch[1].'.state.txt';
681 echo "Getting state file: $sRepURL\n";
682 $sStateFile = file_get_contents($sRepURL);
683 if (!$sStateFile || strlen($sStateFile) > 1000) fail("unable to obtain state file");
684 file_put_contents(CONST_InstallPath.'/settings/state.txt', $sStateFile);
685 echo "Updating DB status\n";
686 pg_query($oDB->connection, 'TRUNCATE import_status');
687 $sSQL = "INSERT INTO import_status VALUES('".$aRepMatch[2]."')";
688 pg_query($oDB->connection, $sSQL);
692 if (!$aCMDResult['all'])
694 fail("Cannot read state file directory.");
700 if ($aCMDResult['index'] || $aCMDResult['all'])
702 $bDidSomething = true;
704 if (isset($aCMDResult['index-output'])) $sOutputFile = ' -F '.$aCMDResult['index-output'];
705 $sBaseCmd = CONST_InstallPath.'/nominatim/nominatim -i -d '.$aDSNInfo['database'].' -P '.$aDSNInfo['port'].' -t '.$iInstances.$sOutputFile;
706 passthruCheckReturn($sBaseCmd.' -R 4');
707 if (!$aCMDResult['index-noanalyse']) pgsqlRunScript('ANALYSE');
708 passthruCheckReturn($sBaseCmd.' -r 5 -R 25');
709 if (!$aCMDResult['index-noanalyse']) pgsqlRunScript('ANALYSE');
710 passthruCheckReturn($sBaseCmd.' -r 26');
713 if ($aCMDResult['create-search-indices'] || $aCMDResult['all'])
715 echo "Search indices\n";
716 $bDidSomething = true;
718 $sSQL = 'select distinct partition from country_name';
719 $aPartitions = $oDB->getCol($sSQL);
720 if (PEAR::isError($aPartitions))
722 fail($aPartitions->getMessage());
724 if (!$aCMDResult['no-partitions']) $aPartitions[] = 0;
726 $sTemplate = file_get_contents(CONST_BasePath.'/sql/indices.src.sql');
727 $sTemplate = replace_tablespace('{ts:address-index}',
728 CONST_Tablespace_Address_Index, $sTemplate);
729 $sTemplate = replace_tablespace('{ts:search-index}',
730 CONST_Tablespace_Search_Index, $sTemplate);
731 $sTemplate = replace_tablespace('{ts:aux-index}',
732 CONST_Tablespace_Aux_Index, $sTemplate);
733 preg_match_all('#^-- start(.*?)^-- end#ms', $sTemplate, $aMatches, PREG_SET_ORDER);
734 foreach($aMatches as $aMatch)
737 foreach($aPartitions as $sPartitionName)
739 $sResult .= str_replace('-partition-', $sPartitionName, $aMatch[1]);
741 $sTemplate = str_replace($aMatch[0], $sResult, $sTemplate);
744 pgsqlRunScript($sTemplate);
747 if (isset($aCMDResult['create-website']))
749 $bDidSomething = true;
750 $sTargetDir = $aCMDResult['create-website'];
751 if (!is_dir($sTargetDir))
753 echo "You must create the website directory before calling this function.\n";
754 fail("Target directory does not exist.");
757 @symlink(CONST_InstallPath.'/website/details.php', $sTargetDir.'/details.php');
758 @symlink(CONST_InstallPath.'/website/reverse.php', $sTargetDir.'/reverse.php');
759 @symlink(CONST_InstallPath.'/website/search.php', $sTargetDir.'/search.php');
760 @symlink(CONST_InstallPath.'/website/search.php', $sTargetDir.'/index.php');
761 @symlink(CONST_InstallPath.'/website/lookup.php', $sTargetDir.'/lookup.php');
762 @symlink(CONST_InstallPath.'/website/deletable.php', $sTargetDir.'/deletable.php');
763 @symlink(CONST_InstallPath.'/website/polygons.php', $sTargetDir.'/polygons.php');
764 @symlink(CONST_InstallPath.'/website/status.php', $sTargetDir.'/status.php');
765 @symlink(CONST_BasePath.'/website/images', $sTargetDir.'/images');
766 @symlink(CONST_BasePath.'/website/js', $sTargetDir.'/js');
767 @symlink(CONST_BasePath.'/website/css', $sTargetDir.'/css');
768 echo "Symlinks created\n";
770 $sTestFile = @file_get_contents(CONST_Website_BaseURL.'js/tiles.js');
773 echo "\nWARNING: Unable to access the website at ".CONST_Website_BaseURL."\n";
774 echo "You may want to update settings/local.php with @define('CONST_Website_BaseURL', 'http://[HOST]/[PATH]/');\n";
778 if ($aCMDResult['drop'])
780 // The implementation is potentially a bit dangerous because it uses
781 // a positive selection of tables to keep, and deletes everything else.
782 // Including any tables that the unsuspecting user might have manually
783 // created. USE AT YOUR OWN PERIL.
784 $bDidSomething = true;
786 // tables we want to keep. everything else goes.
787 $aKeepTables = array(
792 "location_property*",
806 $aDropTables = array();
807 $aHaveTables = $oDB->getCol("SELECT tablename FROM pg_tables WHERE schemaname='public'");
808 if (PEAR::isError($aHaveTables))
810 fail($aPartitions->getMessage());
812 foreach($aHaveTables as $sTable)
815 foreach ($aKeepTables as $sKeep)
817 if (fnmatch($sKeep, $sTable))
823 if (!$bFound) array_push($aDropTables, $sTable);
826 foreach ($aDropTables as $sDrop)
828 if ($aCMDResult['verbose']) echo "dropping table $sDrop\n";
829 @pg_query($oDB->connection, "DROP TABLE $sDrop CASCADE");
830 // ignore warnings/errors as they might be caused by a table having
831 // been deleted already by CASCADE
834 if (!is_null(CONST_Osm2pgsql_Flatnode_File))
836 if ($aCMDResult['verbose']) echo "deleting ".CONST_Osm2pgsql_Flatnode_File."\n";
837 unlink(CONST_Osm2pgsql_Flatnode_File);
843 showUsage($aCMDOptions, true);
847 echo "Setup finished.\n";
850 function pgsqlRunScriptFile($sFilename)
852 if (!file_exists($sFilename)) fail('unable to find '.$sFilename);
854 // Convert database DSN to psql parameters
855 $aDSNInfo = DB::parseDSN(CONST_Database_DSN);
856 if (!isset($aDSNInfo['port']) || !$aDSNInfo['port']) $aDSNInfo['port'] = 5432;
857 $sCMD = 'psql -p '.$aDSNInfo['port'].' -d '.$aDSNInfo['database'];
860 if (preg_match('/\\.gz$/', $sFilename))
862 $aDescriptors = array(
863 0 => array('pipe', 'r'),
864 1 => array('pipe', 'w'),
865 2 => array('file', '/dev/null', 'a')
867 $hGzipProcess = proc_open('zcat '.$sFilename, $aDescriptors, $ahGzipPipes);
868 if (!is_resource($hGzipProcess)) fail('unable to start zcat');
869 $aReadPipe = $ahGzipPipes[1];
870 fclose($ahGzipPipes[0]);
874 $sCMD .= ' -f '.$sFilename;
875 $aReadPipe = array('pipe', 'r');
878 $aDescriptors = array(
880 1 => array('pipe', 'w'),
881 2 => array('file', '/dev/null', 'a')
884 $hProcess = proc_open($sCMD, $aDescriptors, $ahPipes);
885 if (!is_resource($hProcess)) fail('unable to start pgsql');
888 // TODO: error checking
889 while(!feof($ahPipes[1]))
891 echo fread($ahPipes[1], 4096);
895 $iReturn = proc_close($hProcess);
898 fail("pgsql returned with error code ($iReturn)");
902 fclose($ahGzipPipes[1]);
903 proc_close($hGzipProcess);
908 function pgsqlRunScript($sScript, $bfatal = true)
911 // Convert database DSN to psql parameters
912 $aDSNInfo = DB::parseDSN(CONST_Database_DSN);
913 if (!isset($aDSNInfo['port']) || !$aDSNInfo['port']) $aDSNInfo['port'] = 5432;
914 $sCMD = 'psql -p '.$aDSNInfo['port'].' -d '.$aDSNInfo['database'];
915 if ($bfatal && !$aCMDResult['ignore-errors'])
916 $sCMD .= ' -v ON_ERROR_STOP=1';
917 $aDescriptors = array(
918 0 => array('pipe', 'r'),
923 $hProcess = @proc_open($sCMD, $aDescriptors, $ahPipes);
924 if (!is_resource($hProcess)) fail('unable to start pgsql');
926 while(strlen($sScript))
928 $written = fwrite($ahPipes[0], $sScript);
929 if ($written <= 0) break;
930 $sScript = substr($sScript, $written);
933 $iReturn = proc_close($hProcess);
934 if ($bfatal && $iReturn > 0)
936 fail("pgsql returned with error code ($iReturn)");
940 function pgsqlRunRestoreData($sDumpFile)
942 // Convert database DSN to psql parameters
943 $aDSNInfo = DB::parseDSN(CONST_Database_DSN);
944 if (!isset($aDSNInfo['port']) || !$aDSNInfo['port']) $aDSNInfo['port'] = 5432;
945 $sCMD = 'pg_restore -p '.$aDSNInfo['port'].' -d '.$aDSNInfo['database'].' -Fc -a '.$sDumpFile;
947 $aDescriptors = array(
948 0 => array('pipe', 'r'),
949 1 => array('pipe', 'w'),
950 2 => array('file', '/dev/null', 'a')
953 $hProcess = proc_open($sCMD, $aDescriptors, $ahPipes);
954 if (!is_resource($hProcess)) fail('unable to start pg_restore');
958 // TODO: error checking
959 while(!feof($ahPipes[1]))
961 echo fread($ahPipes[1], 4096);
965 $iReturn = proc_close($hProcess);
968 function pgsqlRunDropAndRestore($sDumpFile)
970 // Convert database DSN to psql parameters
971 $aDSNInfo = DB::parseDSN(CONST_Database_DSN);
972 if (!isset($aDSNInfo['port']) || !$aDSNInfo['port']) $aDSNInfo['port'] = 5432;
973 $sCMD = 'pg_restore -p '.$aDSNInfo['port'].' -d '.$aDSNInfo['database'].' -Fc --clean '.$sDumpFile;
975 $aDescriptors = array(
976 0 => array('pipe', 'r'),
977 1 => array('pipe', 'w'),
978 2 => array('file', '/dev/null', 'a')
981 $hProcess = proc_open($sCMD, $aDescriptors, $ahPipes);
982 if (!is_resource($hProcess)) fail('unable to start pg_restore');
986 // TODO: error checking
987 while(!feof($ahPipes[1]))
989 echo fread($ahPipes[1], 4096);
993 $iReturn = proc_close($hProcess);
996 function passthruCheckReturn($cmd)
999 passthru($cmd, $result);
1000 if ($result != 0) fail('Error executing external command: '.$cmd);
1003 function replace_tablespace($sTemplate, $sTablespace, $sSql)
1006 $sSql = str_replace($sTemplate, 'TABLESPACE "'.$sTablespace.'"',
1009 $sSql = str_replace($sTemplate, '', $sSql);