4 require_once(dirname(dirname(__FILE__)).'/settings/settings.php');
5 require_once(CONST_BasePath.'/lib/init-cmd.php');
6 ini_set('memory_limit', '800M');
8 # (long-opt, short-opt, min-occurs, max-occurs, num-arguments, num-arguments, type, help)
12 'Create and setup nominatim search system',
13 array('help', 'h', 0, 1, 0, 0, false, 'Show Help'),
14 array('quiet', 'q', 0, 1, 0, 0, 'bool', 'Quiet output'),
15 array('verbose', 'v', 0, 1, 0, 0, 'bool', 'Verbose output'),
17 array('osm-file', '', 0, 1, 1, 1, 'realpath', 'File to import'),
18 array('threads', '', 0, 1, 1, 1, 'int', 'Number of threads (where possible)'),
20 array('all', '', 0, 1, 0, 0, 'bool', 'Do the complete process'),
22 array('create-db', '', 0, 1, 0, 0, 'bool', 'Create nominatim db'),
23 array('setup-db', '', 0, 1, 0, 0, 'bool', 'Build a blank nominatim db'),
24 array('import-data', '', 0, 1, 0, 0, 'bool', 'Import a osm file'),
25 array('osm2pgsql-cache', '', 0, 1, 1, 1, 'int', 'Cache size used by osm2pgsql'),
26 array('create-functions', '', 0, 1, 0, 0, 'bool', 'Create functions'),
27 array('enable-diff-updates', '', 0, 1, 0, 0, 'bool', 'Turn on the code required to make diff updates work'),
28 array('enable-debug-statements', '', 0, 1, 0, 0, 'bool', 'Include debug warning statements in pgsql commands'),
29 array('ignore-errors', '', 0, 1, 0, 0, 'bool', 'Continue import even when errors in SQL are present (EXPERT)'),
30 array('create-tables', '', 0, 1, 0, 0, 'bool', 'Create main tables'),
31 array('create-partition-tables', '', 0, 1, 0, 0, 'bool', 'Create required partition tables'),
32 array('create-partition-functions', '', 0, 1, 0, 0, 'bool', 'Create required partition triggers'),
33 array('no-partitions', '', 0, 1, 0, 0, 'bool', 'Do not partition search indices (speeds up import of single country extracts)'),
34 array('import-wikipedia-articles', '', 0, 1, 0, 0, 'bool', 'Import wikipedia article dump'),
35 array('load-data', '', 0, 1, 0, 0, 'bool', 'Copy data to live tables from import table'),
36 array('disable-token-precalc', '', 0, 1, 0, 0, 'bool', 'Disable name precalculation (EXPERT)'),
37 array('import-tiger-data', '', 0, 1, 0, 0, 'bool', 'Import tiger data (not included in \'all\')'),
38 array('calculate-postcodes', '', 0, 1, 0, 0, 'bool', 'Calculate postcode centroids'),
39 array('osmosis-init', '', 0, 1, 0, 0, 'bool', 'Generate default osmosis configuration'),
40 array('index', '', 0, 1, 0, 0, 'bool', 'Index the data'),
41 array('index-noanalyse', '', 0, 1, 0, 0, 'bool', 'Do not perform analyse operations during index (EXPERT)'),
42 array('create-search-indices', '', 0, 1, 0, 0, 'bool', 'Create additional indices required for search and update'),
43 array('create-country-names', '', 0, 1, 0, 0, 'bool', 'Create default list of searchable country names'),
44 array('drop', '', 0, 1, 0, 0, 'bool', 'Drop tables needed for updates, making the database readonly (EXPERIMENTAL)'),
46 getCmdOpt($_SERVER['argv'], $aCMDOptions, $aCMDResult, true, true);
48 $bDidSomething = false;
50 // Check if osm-file is set and points to a valid file if --all or --import-data is given
51 if ($aCMDResult['import-data'] || $aCMDResult['all']) {
52 if (!isset($aCMDResult['osm-file'])) {
53 fail('missing --osm-file for data import');
56 if (!file_exists($aCMDResult['osm-file'])) {
57 fail('the path supplied to --osm-file does not exist');
60 if (!is_readable($aCMDResult['osm-file'])) {
61 fail('osm-file "'.$aCMDResult['osm-file'].'" not readable');
65 // by default, use all but one processor, but never more than 15.
66 $iInstances = isset($aCMDResult['threads'])
67 ? $aCMDResult['threads']
68 : (min(16, getProcessorCount()) - 1);
70 if ($iInstances < 1) {
72 warn("resetting threads to $iInstances");
75 // Assume we can steal all the cache memory in the box (unless told otherwise)
76 if (isset($aCMDResult['osm2pgsql-cache'])) {
77 $iCacheMemory = $aCMDResult['osm2pgsql-cache'];
79 $iCacheMemory = getCacheMemoryMB();
82 $sModulePath = CONST_Database_Module_Path;
83 info('module path: ' . $sModulePath);
85 $aDSNInfo = DB::parseDSN(CONST_Database_DSN);
86 if (!isset($aDSNInfo['port']) || !$aDSNInfo['port']) $aDSNInfo['port'] = 5432;
88 if ($aCMDResult['create-db'] || $aCMDResult['all']) {
90 $bDidSomething = true;
91 $oDB = DB::connect(CONST_Database_DSN, false);
92 if (!PEAR::isError($oDB)) {
93 fail('database already exists ('.CONST_Database_DSN.')');
96 $sCreateDBCmd = 'createdb -E UTF-8 -p '.$aDSNInfo['port'].' '.$aDSNInfo['database'];
97 if (isset($aDSNInfo['username']) && $aDSNInfo['username']) {
98 $sCreateDBCmd .= ' -U ' . $aDSNInfo['username'];
100 if (isset($aDSNInfo['hostspec']) && $aDSNInfo['hostspec']) {
101 $sCreateDBCmd .= ' -h ' . $aDSNInfo['hostspec'];
105 if (isset($aDSNInfo['password']) && $aDSNInfo['password']) {
106 $aProcEnv = array_merge(array('PGPASSWORD' => $aDSNInfo['password']), $_ENV);
109 $result = runWithEnv($sCreateDBCmd, $aProcEnv);
110 if ($result != 0) fail('Error executing external command: '.$sCreateDBCmd);
113 if ($aCMDResult['setup-db'] || $aCMDResult['all']) {
115 $bDidSomething = true;
119 $fPostgresVersion = getPostgresVersion($oDB);
120 echo 'Postgres version found: '.$fPostgresVersion."\n";
122 if ($fPostgresVersion < 9.1) {
123 fail('Minimum supported version of Postgresql is 9.1.');
126 pgsqlRunScript('CREATE EXTENSION IF NOT EXISTS hstore');
127 pgsqlRunScript('CREATE EXTENSION IF NOT EXISTS postgis');
129 // For extratags and namedetails the hstore_to_json converter is
130 // needed which is only available from Postgresql 9.3+. For older
131 // versions add a dummy function that returns nothing.
132 $iNumFunc = chksql($oDB->getOne("select count(*) from pg_proc where proname = 'hstore_to_json'"));
134 if ($iNumFunc == 0) {
135 pgsqlRunScript("create function hstore_to_json(dummy hstore) returns text AS 'select null::text' language sql immutable");
136 warn('Postgresql is too old. extratags and namedetails API not available.');
139 $fPostgisVersion = getPostgisVersion($oDB);
140 echo 'Postgis version found: '.$fPostgisVersion."\n";
142 if ($fPostgisVersion < 2.1) {
143 // Functions were renamed in 2.1 and throw an annoying deprecation warning
144 pgsqlRunScript('ALTER FUNCTION st_line_interpolate_point(geometry, double precision) RENAME TO ST_LineInterpolatePoint');
145 pgsqlRunScript('ALTER FUNCTION ST_Line_Locate_Point(geometry, geometry) RENAME TO ST_LineLocatePoint');
147 if ($fPostgisVersion < 2.2) {
148 pgsqlRunScript('ALTER FUNCTION ST_Distance_Spheroid(geometry, geometry, spheroid) RENAME TO ST_DistanceSpheroid');
151 $i = chksql($oDB->getOne("select count(*) from pg_user where usename = '".CONST_Database_Web_User."'"));
153 echo "\nERROR: Web user '".CONST_Database_Web_User."' does not exist. Create it with:\n";
154 echo "\n createuser ".CONST_Database_Web_User."\n\n";
158 if (!checkModulePresence()) {
159 fail('error loading nominatim.so module');
162 if (!file_exists(CONST_ExtraDataPath.'/country_osm_grid.sql.gz')) {
163 echo 'Error: you need to download the country_osm_grid first:';
164 echo "\n wget -O ".CONST_ExtraDataPath."/country_osm_grid.sql.gz https://www.nominatim.org/data/country_grid.sql.gz\n";
168 pgsqlRunScriptFile(CONST_BasePath.'/data/country_name.sql');
169 pgsqlRunScriptFile(CONST_BasePath.'/data/country_naturalearthdata.sql');
170 pgsqlRunScriptFile(CONST_BasePath.'/data/country_osm_grid.sql.gz');
171 pgsqlRunScriptFile(CONST_BasePath.'/data/gb_postcode_table.sql');
172 if (file_exists(CONST_BasePath.'/data/gb_postcode_data.sql.gz')) {
173 pgsqlRunScriptFile(CONST_BasePath.'/data/gb_postcode_data.sql.gz');
175 warn('external UK postcode table not found.');
177 if (CONST_Use_Extra_US_Postcodes) {
178 pgsqlRunScriptFile(CONST_BasePath.'/data/us_postcode.sql');
181 if ($aCMDResult['no-partitions']) {
182 pgsqlRunScript('update country_name set partition = 0');
185 // the following will be needed by create_functions later but
186 // is only defined in the subsequently called create_tables.
187 // Create dummies here that will be overwritten by the proper
188 // versions in create-tables.
189 pgsqlRunScript('CREATE TABLE IF NOT EXISTS place_boundingbox ()');
190 pgsqlRunScript('CREATE TYPE wikipedia_article_match AS ()', false);
193 if ($aCMDResult['import-data'] || $aCMDResult['all']) {
195 $bDidSomething = true;
197 $osm2pgsql = CONST_Osm2pgsql_Binary;
198 if (!file_exists($osm2pgsql)) {
199 echo "Check CONST_Osm2pgsql_Binary in your local settings file.\n";
200 echo "Normally you should not need to set this manually.\n";
201 fail("osm2pgsql not found in '$osm2pgsql'");
204 if (!is_null(CONST_Osm2pgsql_Flatnode_File) && CONST_Osm2pgsql_Flatnode_File) {
205 $osm2pgsql .= ' --flat-nodes '.CONST_Osm2pgsql_Flatnode_File;
207 if (CONST_Tablespace_Osm2pgsql_Data)
208 $osm2pgsql .= ' --tablespace-slim-data '.CONST_Tablespace_Osm2pgsql_Data;
209 if (CONST_Tablespace_Osm2pgsql_Index)
210 $osm2pgsql .= ' --tablespace-slim-index '.CONST_Tablespace_Osm2pgsql_Index;
211 if (CONST_Tablespace_Place_Data)
212 $osm2pgsql .= ' --tablespace-main-data '.CONST_Tablespace_Place_Data;
213 if (CONST_Tablespace_Place_Index)
214 $osm2pgsql .= ' --tablespace-main-index '.CONST_Tablespace_Place_Index;
215 $osm2pgsql .= ' -lsc -O gazetteer --hstore --number-processes 1';
216 $osm2pgsql .= ' -C '.$iCacheMemory;
217 $osm2pgsql .= ' -P '.$aDSNInfo['port'];
218 if (isset($aDSNInfo['username']) && $aDSNInfo['username']) {
219 $osm2pgsql .= ' -U ' . $aDSNInfo['username'];
221 if (isset($aDSNInfo['hostspec']) && $aDSNInfo['hostspec']) {
222 $osm2pgsql .= ' -H ' . $aDSNInfo['hostspec'];
226 if (isset($aDSNInfo['password']) && $aDSNInfo['password']) {
227 $aProcEnv = array_merge(array('PGPASSWORD' => $aDSNInfo['password']), $_ENV);
230 $osm2pgsql .= ' -d '.$aDSNInfo['database'].' '.$aCMDResult['osm-file'];
231 runWithEnv($osm2pgsql, $aProcEnv);
234 if (!$aCMDResult['ignore-errors'] && !chksql($oDB->getRow('select * from place limit 1'))) {
239 if ($aCMDResult['create-functions'] || $aCMDResult['all']) {
240 info('Create Functions');
241 $bDidSomething = true;
243 if (!checkModulePresence()) {
244 fail('error loading nominatim.so module');
247 create_sql_functions($aCMDResult);
250 if ($aCMDResult['create-tables'] || $aCMDResult['all']) {
251 info('Create Tables');
252 $bDidSomething = true;
254 $sTemplate = file_get_contents(CONST_BasePath.'/sql/tables.sql');
255 $sTemplate = str_replace('{www-user}', CONST_Database_Web_User, $sTemplate);
256 $sTemplate = replace_tablespace(
258 CONST_Tablespace_Address_Data,
261 $sTemplate = replace_tablespace(
262 '{ts:address-index}',
263 CONST_Tablespace_Address_Index,
266 $sTemplate = replace_tablespace(
268 CONST_Tablespace_Search_Data,
271 $sTemplate = replace_tablespace(
273 CONST_Tablespace_Search_Index,
276 $sTemplate = replace_tablespace(
278 CONST_Tablespace_Aux_Data,
281 $sTemplate = replace_tablespace(
283 CONST_Tablespace_Aux_Index,
286 pgsqlRunScript($sTemplate, false);
288 // re-run the functions
289 info('Recreate Functions');
290 create_sql_functions($aCMDResult);
293 if ($aCMDResult['create-partition-tables'] || $aCMDResult['all']) {
294 info('Create Partition Tables');
295 $bDidSomething = true;
297 $sTemplate = file_get_contents(CONST_BasePath.'/sql/partition-tables.src.sql');
298 $sTemplate = replace_tablespace(
300 CONST_Tablespace_Address_Data,
303 $sTemplate = replace_tablespace(
304 '{ts:address-index}',
305 CONST_Tablespace_Address_Index,
308 $sTemplate = replace_tablespace(
310 CONST_Tablespace_Search_Data,
313 $sTemplate = replace_tablespace(
315 CONST_Tablespace_Search_Index,
318 $sTemplate = replace_tablespace(
320 CONST_Tablespace_Aux_Data,
323 $sTemplate = replace_tablespace(
325 CONST_Tablespace_Aux_Index,
329 pgsqlRunPartitionScript($sTemplate);
333 if ($aCMDResult['create-partition-functions'] || $aCMDResult['all']) {
334 info('Create Partition Functions');
335 $bDidSomething = true;
337 $sTemplate = file_get_contents(CONST_BasePath.'/sql/partition-functions.src.sql');
339 pgsqlRunPartitionScript($sTemplate);
342 if ($aCMDResult['import-wikipedia-articles'] || $aCMDResult['all']) {
343 $bDidSomething = true;
344 $sWikiArticlesFile = CONST_Wikipedia_Data_Path.'/wikipedia_article.sql.bin';
345 $sWikiRedirectsFile = CONST_Wikipedia_Data_Path.'/wikipedia_redirect.sql.bin';
346 if (file_exists($sWikiArticlesFile)) {
347 info('Importing wikipedia articles');
348 pgsqlRunDropAndRestore($sWikiArticlesFile);
350 warn('wikipedia article dump file not found - places will have default importance');
352 if (file_exists($sWikiRedirectsFile)) {
353 info('Importing wikipedia redirects');
354 pgsqlRunDropAndRestore($sWikiRedirectsFile);
356 warn('wikipedia redirect dump file not found - some place importance values may be missing');
361 if ($aCMDResult['load-data'] || $aCMDResult['all']) {
362 info('Drop old Data');
363 $bDidSomething = true;
366 if (!pg_query($oDB->connection, 'TRUNCATE word')) fail(pg_last_error($oDB->connection));
368 if (!pg_query($oDB->connection, 'TRUNCATE placex')) fail(pg_last_error($oDB->connection));
370 if (!pg_query($oDB->connection, 'TRUNCATE location_property_osmline')) fail(pg_last_error($oDB->connection));
372 if (!pg_query($oDB->connection, 'TRUNCATE place_addressline')) fail(pg_last_error($oDB->connection));
374 if (!pg_query($oDB->connection, 'TRUNCATE place_boundingbox')) fail(pg_last_error($oDB->connection));
376 if (!pg_query($oDB->connection, 'TRUNCATE location_area')) fail(pg_last_error($oDB->connection));
378 if (!pg_query($oDB->connection, 'TRUNCATE search_name')) fail(pg_last_error($oDB->connection));
380 if (!pg_query($oDB->connection, 'TRUNCATE search_name_blank')) fail(pg_last_error($oDB->connection));
382 if (!pg_query($oDB->connection, 'DROP SEQUENCE seq_place')) fail(pg_last_error($oDB->connection));
384 if (!pg_query($oDB->connection, 'CREATE SEQUENCE seq_place start 100000')) fail(pg_last_error($oDB->connection));
387 $sSQL = 'select distinct partition from country_name';
388 $aPartitions = chksql($oDB->getCol($sSQL));
389 if (!$aCMDResult['no-partitions']) $aPartitions[] = 0;
390 foreach ($aPartitions as $sPartition) {
391 if (!pg_query($oDB->connection, 'TRUNCATE location_road_'.$sPartition)) fail(pg_last_error($oDB->connection));
395 // used by getorcreate_word_id to ignore frequent partial words
396 $sSQL = 'CREATE OR REPLACE FUNCTION get_maxwordfreq() RETURNS integer AS ';
397 $sSQL .= '$$ SELECT '.CONST_Max_Word_Frequency.' as maxwordfreq; $$ LANGUAGE SQL IMMUTABLE';
398 if (!pg_query($oDB->connection, $sSQL)) {
399 fail(pg_last_error($oDB->connection));
403 // pre-create the word list
404 if (!$aCMDResult['disable-token-precalc']) {
405 info('Loading word list');
406 pgsqlRunScriptFile(CONST_BasePath.'/data/words.sql');
410 $sColumns = 'osm_type, osm_id, class, type, name, admin_level, address, extratags, geometry';
412 $aDBInstances = array();
413 $iLoadThreads = max(1, $iInstances - 1);
414 for ($i = 0; $i < $iLoadThreads; $i++) {
415 $aDBInstances[$i] =& getDB(true);
416 $sSQL = "INSERT INTO placex ($sColumns) SELECT $sColumns FROM place WHERE osm_id % $iLoadThreads = $i";
417 $sSQL .= " and not (class='place' and type='houses' and osm_type='W'";
418 $sSQL .= " and ST_GeometryType(geometry) = 'ST_LineString')";
419 $sSQL .= ' and ST_IsValid(geometry)';
420 if ($aCMDResult['verbose']) echo "$sSQL\n";
421 if (!pg_send_query($aDBInstances[$i]->connection, $sSQL)) {
422 fail(pg_last_error($aDBInstances[$i]->connection));
425 // last thread for interpolation lines
426 $aDBInstances[$iLoadThreads] =& getDB(true);
427 $sSQL = 'insert into location_property_osmline';
428 $sSQL .= ' (osm_id, address, linegeo)';
429 $sSQL .= ' SELECT osm_id, address, geometry from place where ';
430 $sSQL .= "class='place' and type='houses' and osm_type='W' and ST_GeometryType(geometry) = 'ST_LineString'";
431 if ($aCMDResult['verbose']) echo "$sSQL\n";
432 if (!pg_send_query($aDBInstances[$iLoadThreads]->connection, $sSQL)) {
433 fail(pg_last_error($aDBInstances[$iLoadThreads]->connection));
437 for ($i = 0; $i <= $iLoadThreads; $i++) {
438 while (($hPGresult = pg_get_result($aDBInstances[$i]->connection)) !== false) {
439 $resultStatus = pg_result_status($hPGresult);
440 // PGSQL_EMPTY_QUERY, PGSQL_COMMAND_OK, PGSQL_TUPLES_OK,
441 // PGSQL_COPY_OUT, PGSQL_COPY_IN, PGSQL_BAD_RESPONSE,
442 // PGSQL_NONFATAL_ERROR and PGSQL_FATAL_ERROR
443 if ($resultStatus != PGSQL_COMMAND_OK && $resultStatus != PGSQL_TUPLES_OK) {
444 $resultError = pg_result_error($hPGresult);
445 echo '-- error text ' . $i . ': ' . $resultError . "\n";
451 fail('SQL errors loading placex and/or location_property_osmline tables');
454 info('Reanalysing database');
455 pgsqlRunScript('ANALYSE');
457 $sDatabaseDate = getDatabaseDate($oDB);
458 pg_query($oDB->connection, 'TRUNCATE import_status');
459 if ($sDatabaseDate === false) {
460 warn('could not determine database date.');
462 $sSQL = "INSERT INTO import_status (lastimportdate) VALUES('".$sDatabaseDate."')";
463 pg_query($oDB->connection, $sSQL);
464 echo "Latest data imported from $sDatabaseDate.\n";
468 if ($aCMDResult['import-tiger-data']) {
469 info('Import Tiger data');
470 $bDidSomething = true;
472 $sTemplate = file_get_contents(CONST_BasePath.'/sql/tiger_import_start.sql');
473 $sTemplate = str_replace('{www-user}', CONST_Database_Web_User, $sTemplate);
474 $sTemplate = replace_tablespace(
476 CONST_Tablespace_Aux_Data,
479 $sTemplate = replace_tablespace(
481 CONST_Tablespace_Aux_Index,
484 pgsqlRunScript($sTemplate, false);
486 $aDBInstances = array();
487 for ($i = 0; $i < $iInstances; $i++) {
488 $aDBInstances[$i] =& getDB(true);
491 foreach (glob(CONST_Tiger_Data_Path.'/*.sql') as $sFile) {
493 $hFile = fopen($sFile, 'r');
494 $sSQL = fgets($hFile, 100000);
498 for ($i = 0; $i < $iInstances; $i++) {
499 if (!pg_connection_busy($aDBInstances[$i]->connection)) {
500 while (pg_get_result($aDBInstances[$i]->connection));
501 $sSQL = fgets($hFile, 100000);
503 if (!pg_send_query($aDBInstances[$i]->connection, $sSQL)) fail(pg_last_error($oDB->connection));
505 if ($iLines == 1000) {
519 for ($i = 0; $i < $iInstances; $i++) {
520 if (pg_connection_busy($aDBInstances[$i]->connection)) $bAnyBusy = true;
527 info('Creating indexes on Tiger data');
528 $sTemplate = file_get_contents(CONST_BasePath.'/sql/tiger_import_finish.sql');
529 $sTemplate = str_replace('{www-user}', CONST_Database_Web_User, $sTemplate);
530 $sTemplate = replace_tablespace(
532 CONST_Tablespace_Aux_Data,
535 $sTemplate = replace_tablespace(
537 CONST_Tablespace_Aux_Index,
540 pgsqlRunScript($sTemplate, false);
543 if ($aCMDResult['calculate-postcodes'] || $aCMDResult['all']) {
544 info('Calculate Postcodes');
545 $bDidSomething = true;
547 if (!pg_query($oDB->connection, 'TRUNCATE location_postcode')) {
548 fail(pg_last_error($oDB->connection));
551 $sSQL = 'INSERT INTO location_postcode';
552 $sSQL .= ' (place_id, indexed_status, country_code, postcode, geometry) ';
553 $sSQL .= "SELECT nextval('seq_place'), 1, country_code,";
554 $sSQL .= " upper(trim (both ' ' from address->'postcode')) as pc,";
555 $sSQL .= ' ST_Centroid(ST_Collect(ST_Centroid(geometry)))';
556 $sSQL .= ' FROM placex';
557 $sSQL .= " WHERE address ? 'postcode' AND address->'postcode' NOT SIMILAR TO '%(,|;)%'";
558 $sSQL .= ' AND geometry IS NOT null';
559 $sSQL .= ' GROUP BY country_code, pc';
561 if (!pg_query($oDB->connection, $sSQL)) {
562 fail(pg_last_error($oDB->connection));
565 if (CONST_Use_Extra_US_Postcodes) {
566 // only add postcodes that are not yet available in OSM
567 $sSQL = 'INSERT INTO location_postcode';
568 $sSQL .= ' (place_id, indexed_status, country_code, postcode, geometry) ';
569 $sSQL .= "SELECT nextval('seq_place'), 1, 'us', postcode,";
570 $sSQL .= ' ST_SetSRID(ST_Point(x,y),4326)';
571 $sSQL .= ' FROM us_postcode WHERE postcode NOT IN';
572 $sSQL .= ' (SELECT postcode FROM location_postcode';
573 $sSQL .= " WHERE country_code = 'us')";
574 if (!pg_query($oDB->connection, $sSQL)) fail(pg_last_error($oDB->connection));
577 // add missing postcodes for GB (if available)
578 $sSQL = 'INSERT INTO location_postcode';
579 $sSQL .= ' (place_id, indexed_status, country_code, postcode, geometry) ';
580 $sSQL .= "SELECT nextval('seq_place'), 1, 'gb', postcode, geometry";
581 $sSQL .= ' FROM gb_postcode WHERE postcode NOT IN';
582 $sSQL .= ' (SELECT postcode FROM location_postcode';
583 $sSQL .= " WHERE country_code = 'gb')";
584 if (!pg_query($oDB->connection, $sSQL)) fail(pg_last_error($oDB->connection));
586 if (!$aCMDResult['all']) {
587 $sSQL = "DELETE FROM word WHERE class='place' and type='postcode'";
588 $sSQL .= 'and word NOT IN (SELECT postcode FROM location_postcode)';
589 if (!pg_query($oDB->connection, $sSQL)) {
590 fail(pg_last_error($oDB->connection));
593 $sSQL = 'SELECT count(getorcreate_postcode_id(v)) FROM ';
594 $sSQL .= '(SELECT distinct(postcode) as v FROM location_postcode) p';
596 if (!pg_query($oDB->connection, $sSQL)) {
597 fail(pg_last_error($oDB->connection));
601 if ($aCMDResult['osmosis-init']) {
602 $bDidSomething = true;
603 echo "Command 'osmosis-init' no longer available, please use utils/update.php --init-updates.\n";
606 if ($aCMDResult['index'] || $aCMDResult['all']) {
607 $bDidSomething = true;
609 $sBaseCmd = CONST_InstallPath.'/nominatim/nominatim -i -d '.$aDSNInfo['database'].' -P '.$aDSNInfo['port'].' -t '.$iInstances.$sOutputFile;
610 if (isset($aDSNInfo['hostspec']) && $aDSNInfo['hostspec']) {
611 $sBaseCmd .= ' -H ' . $aDSNInfo['hostspec'];
613 if (isset($aDSNInfo['username']) && $aDSNInfo['username']) {
614 $sBaseCmd .= ' -U ' . $aDSNInfo['username'];
617 if (isset($aDSNInfo['password']) && $aDSNInfo['password']) {
618 $aProcEnv = array_merge(array('PGPASSWORD' => $aDSNInfo['password']), $_ENV);
621 info('Index ranks 0 - 4');
622 $iStatus = runWithEnv($sBaseCmd.' -R 4', $aProcEnv);
624 fail('error status ' . $iStatus . ' running nominatim!');
626 if (!$aCMDResult['index-noanalyse']) pgsqlRunScript('ANALYSE');
627 info('Index ranks 5 - 25');
628 $iStatus = runWithEnv($sBaseCmd.' -r 5 -R 25', $aProcEnv);
630 fail('error status ' . $iStatus . ' running nominatim!');
632 if (!$aCMDResult['index-noanalyse']) pgsqlRunScript('ANALYSE');
633 info('Index ranks 26 - 30');
634 $iStatus = runWithEnv($sBaseCmd.' -r 26', $aProcEnv);
636 fail('error status ' . $iStatus . ' running nominatim!');
639 info('Index postcodes');
641 $sSQL = 'UPDATE location_postcode SET indexed_status = 0';
642 if (!pg_query($oDB->connection, $sSQL)) fail(pg_last_error($oDB->connection));
645 if ($aCMDResult['create-search-indices'] || $aCMDResult['all']) {
646 info('Create Search indices');
647 $bDidSomething = true;
649 $sTemplate = file_get_contents(CONST_BasePath.'/sql/indices.src.sql');
650 $sTemplate = str_replace('{www-user}', CONST_Database_Web_User, $sTemplate);
651 $sTemplate = replace_tablespace(
652 '{ts:address-index}',
653 CONST_Tablespace_Address_Index,
656 $sTemplate = replace_tablespace(
658 CONST_Tablespace_Search_Index,
661 $sTemplate = replace_tablespace(
663 CONST_Tablespace_Aux_Index,
667 pgsqlRunScript($sTemplate);
670 if ($aCMDResult['create-country-names'] || $aCMDResult['all']) {
671 info('Create search index for default country names');
672 $bDidSomething = true;
674 pgsqlRunScript("select getorcreate_country(make_standard_name('uk'), 'gb')");
675 pgsqlRunScript("select getorcreate_country(make_standard_name('united states'), 'us')");
676 pgsqlRunScript('select count(*) from (select getorcreate_country(make_standard_name(country_code), country_code) from country_name where country_code is not null) as x');
677 pgsqlRunScript("select count(*) from (select getorcreate_country(make_standard_name(name->'name'), country_code) from country_name where name ? 'name') as x");
679 $sSQL = 'select count(*) from (select getorcreate_country(make_standard_name(v), country_code) from (select country_code, skeys(name) as k, svals(name) as v from country_name) x where k ';
680 if (CONST_Languages) {
683 foreach (explode(',', CONST_Languages) as $sLang) {
684 $sSQL .= $sDelim."'name:$sLang'";
689 // all include all simple name tags
690 $sSQL .= "like 'name:%'";
693 pgsqlRunScript($sSQL);
696 if ($aCMDResult['drop']) {
697 info('Drop tables only required for updates');
698 // The implementation is potentially a bit dangerous because it uses
699 // a positive selection of tables to keep, and deletes everything else.
700 // Including any tables that the unsuspecting user might have manually
701 // created. USE AT YOUR OWN PERIL.
702 $bDidSomething = true;
704 // tables we want to keep. everything else goes.
705 $aKeepTables = array(
711 'location_property*',
724 $aDropTables = array();
725 $aHaveTables = chksql($oDB->getCol("SELECT tablename FROM pg_tables WHERE schemaname='public'"));
727 foreach ($aHaveTables as $sTable) {
729 foreach ($aKeepTables as $sKeep) {
730 if (fnmatch($sKeep, $sTable)) {
735 if (!$bFound) array_push($aDropTables, $sTable);
738 foreach ($aDropTables as $sDrop) {
739 if ($aCMDResult['verbose']) echo "dropping table $sDrop\n";
740 @pg_query($oDB->connection, "DROP TABLE $sDrop CASCADE");
741 // ignore warnings/errors as they might be caused by a table having
742 // been deleted already by CASCADE
745 if (!is_null(CONST_Osm2pgsql_Flatnode_File) && CONST_Osm2pgsql_Flatnode_File) {
746 if ($aCMDResult['verbose']) echo 'deleting '.CONST_Osm2pgsql_Flatnode_File."\n";
747 unlink(CONST_Osm2pgsql_Flatnode_File);
751 if (!$bDidSomething) {
752 showUsage($aCMDOptions, true);
754 echo "Summary of warnings:\n\n";
757 info('Setup finished.');
761 function pgsqlRunScriptFile($sFilename)
764 if (!file_exists($sFilename)) fail('unable to find '.$sFilename);
766 // Convert database DSN to psql parameters
767 $aDSNInfo = DB::parseDSN(CONST_Database_DSN);
768 if (!isset($aDSNInfo['port']) || !$aDSNInfo['port']) $aDSNInfo['port'] = 5432;
769 $sCMD = 'psql -p '.$aDSNInfo['port'].' -d '.$aDSNInfo['database'];
770 if (!$aCMDResult['verbose']) {
773 if (isset($aDSNInfo['hostspec']) && $aDSNInfo['hostspec']) {
774 $sCMD .= ' -h ' . $aDSNInfo['hostspec'];
776 if (isset($aDSNInfo['username']) && $aDSNInfo['username']) {
777 $sCMD .= ' -U ' . $aDSNInfo['username'];
780 if (isset($aDSNInfo['password']) && $aDSNInfo['password']) {
781 $aProcEnv = array_merge(array('PGPASSWORD' => $aDSNInfo['password']), $_ENV);
785 if (preg_match('/\\.gz$/', $sFilename)) {
786 $aDescriptors = array(
787 0 => array('pipe', 'r'),
788 1 => array('pipe', 'w'),
789 2 => array('file', '/dev/null', 'a')
791 $hGzipProcess = proc_open('zcat '.$sFilename, $aDescriptors, $ahGzipPipes);
792 if (!is_resource($hGzipProcess)) fail('unable to start zcat');
793 $aReadPipe = $ahGzipPipes[1];
794 fclose($ahGzipPipes[0]);
796 $sCMD .= ' -f '.$sFilename;
797 $aReadPipe = array('pipe', 'r');
800 $aDescriptors = array(
802 1 => array('pipe', 'w'),
803 2 => array('file', '/dev/null', 'a')
806 $hProcess = proc_open($sCMD, $aDescriptors, $ahPipes, null, $aProcEnv);
807 if (!is_resource($hProcess)) fail('unable to start pgsql');
809 // TODO: error checking
810 while (!feof($ahPipes[1])) {
811 echo fread($ahPipes[1], 4096);
815 $iReturn = proc_close($hProcess);
817 fail("pgsql returned with error code ($iReturn)");
820 fclose($ahGzipPipes[1]);
821 proc_close($hGzipProcess);
825 function pgsqlRunScript($sScript, $bfatal = true)
831 $aCMDResult['verbose'],
832 $aCMDResult['ignore-errors']
836 function pgsqlRunPartitionScript($sTemplate)
841 $sSQL = 'select distinct partition from country_name';
842 $aPartitions = chksql($oDB->getCol($sSQL));
843 if (!$aCMDResult['no-partitions']) $aPartitions[] = 0;
845 preg_match_all('#^-- start(.*?)^-- end#ms', $sTemplate, $aMatches, PREG_SET_ORDER);
846 foreach ($aMatches as $aMatch) {
848 foreach ($aPartitions as $sPartitionName) {
849 $sResult .= str_replace('-partition-', $sPartitionName, $aMatch[1]);
851 $sTemplate = str_replace($aMatch[0], $sResult, $sTemplate);
854 pgsqlRunScript($sTemplate);
857 function pgsqlRunRestoreData($sDumpFile)
859 // Convert database DSN to psql parameters
860 $aDSNInfo = DB::parseDSN(CONST_Database_DSN);
861 if (!isset($aDSNInfo['port']) || !$aDSNInfo['port']) $aDSNInfo['port'] = 5432;
862 $sCMD = 'pg_restore -p '.$aDSNInfo['port'].' -d '.$aDSNInfo['database'].' -Fc -a '.$sDumpFile;
864 $aDescriptors = array(
865 0 => array('pipe', 'r'),
866 1 => array('pipe', 'w'),
867 2 => array('file', '/dev/null', 'a')
870 $hProcess = proc_open($sCMD, $aDescriptors, $ahPipes);
871 if (!is_resource($hProcess)) fail('unable to start pg_restore');
875 // TODO: error checking
876 while (!feof($ahPipes[1])) {
877 echo fread($ahPipes[1], 4096);
881 $iReturn = proc_close($hProcess);
884 function pgsqlRunDropAndRestore($sDumpFile)
886 // Convert database DSN to psql parameters
887 $aDSNInfo = DB::parseDSN(CONST_Database_DSN);
888 if (!isset($aDSNInfo['port']) || !$aDSNInfo['port']) $aDSNInfo['port'] = 5432;
889 $sCMD = 'pg_restore -p '.$aDSNInfo['port'].' -d '.$aDSNInfo['database'].' -Fc --clean '.$sDumpFile;
890 if (isset($aDSNInfo['hostspec']) && $aDSNInfo['hostspec']) {
891 $sCMD .= ' -h ' . $aDSNInfo['hostspec'];
893 if (isset($aDSNInfo['username']) && $aDSNInfo['username']) {
894 $sCMD .= ' -U ' . $aDSNInfo['username'];
897 if (isset($aDSNInfo['password']) && $aDSNInfo['password']) {
898 $aProcEnv = array_merge(array('PGPASSWORD' => $aDSNInfo['password']), $_ENV);
901 $iReturn = runWithEnv($sCMD, $aProcEnv);
904 function passthruCheckReturn($sCmd)
907 passthru($sCmd, $iResult);
910 function replace_tablespace($sTemplate, $sTablespace, $sSql)
913 $sSql = str_replace($sTemplate, 'TABLESPACE "'.$sTablespace.'"', $sSql);
915 $sSql = str_replace($sTemplate, '', $sSql);
921 function create_sql_functions($aCMDResult)
924 $sTemplate = file_get_contents(CONST_BasePath.'/sql/functions.sql');
925 $sTemplate = str_replace('{modulepath}', $sModulePath, $sTemplate);
926 if ($aCMDResult['enable-diff-updates']) {
927 $sTemplate = str_replace('RETURN NEW; -- %DIFFUPDATES%', '--', $sTemplate);
929 if ($aCMDResult['enable-debug-statements']) {
930 $sTemplate = str_replace('--DEBUG:', '', $sTemplate);
932 if (CONST_Limit_Reindexing) {
933 $sTemplate = str_replace('--LIMIT INDEXING:', '', $sTemplate);
935 if (!CONST_Use_US_Tiger_Data) {
936 $sTemplate = str_replace('-- %NOTIGERDATA% ', '', $sTemplate);
938 if (!CONST_Use_Aux_Location_data) {
939 $sTemplate = str_replace('-- %NOAUXDATA% ', '', $sTemplate);
941 pgsqlRunScript($sTemplate);
944 function checkModulePresence()
946 // Try accessing the C module, so we know early if something is wrong
947 // and can simply error out.
949 $sSQL = "CREATE FUNCTION nominatim_test_import_func(text) RETURNS text AS '";
950 $sSQL .= $sModulePath."/nominatim.so', 'transliteration' LANGUAGE c IMMUTABLE STRICT";
951 $sSQL .= ';DROP FUNCTION nominatim_test_import_func(text);';
954 $oResult = $oDB->query($sSQL);
958 if (PEAR::isError($oResult)) {
959 echo "\nERROR: Failed to load nominatim module. Reason:\n";
960 echo $oResult->userinfo."\n\n";