]> git.openstreetmap.org Git - nominatim.git/blob - utils/setupClass.php
first draft of setupClass
[nominatim.git] / utils / setupClass.php
1 <?php\r
2 \r
3 \r
4 \r
5 class SetupFunctions\r
6 \r
7 {\r
8     protected $iCacheMemory;            // set in constructor\r
9     protected $iInstances;              // set in constructor\r
10     protected $sModulePath;             // set in constructor\r
11     protected $aDSNInfo;                // set in constructor = DB::parseDSN(CONST_Database_DSN);\r
12     protected $sVerbose;                // set in constructor\r
13     protected $sIgnoreErrors;           // set in constructor\r
14     protected $bEnableDiffUpdates;      // set in constructor\r
15     protected $bEnableDebugStatements;  // set in constructor\r
16     protected $bNoPartitions;           // set in constructor\r
17     protected $oDB = null;              // set in setupDB (earliest) or later in loadData, importData, drop, createSqlFunctions, importTigerData\r
18                                                 // pgsqlRunPartitionScript, calculatePostcodes, ..if no already set \r
19 \r
20 \r
21     public function __construct($aCMDResult) {\r
22         // by default, use all but one processor, but never more than 15.\r
23         $this->iInstances = isset($aCMDResult['threads'])\r
24             ? $aCMDResult['threads']\r
25             : (min(16, getProcessorCount()) - 1);\r
26 \r
27 \r
28         if ($this->iInstances < 1) {\r
29             $this->iInstances = 1;\r
30             warn('resetting threads to '.$this->iInstances);\r
31         }\r
32 \r
33         // Assume we can steal all the cache memory in the box (unless told otherwise)\r
34         if (isset($aCMDResult['osm2pgsql-cache'])) {\r
35             $this->iCacheMemory = $aCMDResult['osm2pgsql-cache'];\r
36         } else {\r
37             $this->iCacheMemory = getCacheMemoryMB();\r
38         }\r
39 \r
40         $this->sModulePath = CONST_Database_Module_Path;\r
41         info('module path: ' . $this->sModulePath);\r
42        \r
43         // prepares DB for import or update, sets the Data Source Name\r
44         $this->aDSNInfo = DB::parseDSN(CONST_Database_DSN);\r
45         if (!isset($this->aDSNInfo['port']) || !$this->aDSNInfo['port']) $this->aDSNInfo['port'] = 5432;\r
46  \r
47         // setting member variables based on command line options stored in $aCMDResult\r
48         $this->sVerbose = $aCMDResult['verbose'];\r
49         $this->sIgnoreErrors = $aCMDResult['ignore-errors'];\r
50         $this->bEnableDiffUpdates = $aCMDResult['enable-diff-updates'];\r
51         $this->bEnableDebugStatements = $aCMDResult['enable-debug-statements'];\r
52         $this->bNoPartitions = $aCMDResult['no-partitions'];\r
53  \r
54     }\r
55 \r
56     public function createDB(){\r
57         info('Create DB');\r
58         $sDB = DB::connect(CONST_Database_DSN, false);\r
59         if (!PEAR::isError($sDB)) {\r
60             fail('database already exists ('.CONST_Database_DSN.')');\r
61         }\r
62 \r
63         $sCreateDBCmd = 'createdb -E UTF-8 -p '.$this->aDSNInfo['port'].' '.$this->aDSNInfo['database'];\r
64         if (isset($this->aDSNInfo['username']) && $this->aDSNInfo['username']) {\r
65             $sCreateDBCmd .= ' -U ' . $this->aDSNInfo['username'];\r
66         }\r
67 \r
68         if (isset($this->aDSNInfo['hostspec']) && $this->aDSNInfo['hostspec']) {\r
69             $sCreateDBCmd .= ' -h ' . $this->aDSNInfo['hostspec'];\r
70         }\r
71 \r
72         $aProcEnv = null;\r
73         if (isset($this->aDSNInfo['password']) && $this->aDSNInfo['password']) {\r
74             $aProcEnv = array_merge(array('PGPASSWORD' => $this->aDSNInfo['password']), $_ENV);\r
75         }\r
76 \r
77         $result = runWithEnv($sCreateDBCmd, $aProcEnv);\r
78         if ($result != 0) fail('Error executing external command: '.$sCreateDBCmd);\r
79     }\r
80 \r
81     public function setupDB() {\r
82         info('Setup DB');\r
83         $this->oDB =& getDB();\r
84 \r
85         $fPostgresVersion = getPostgresVersion($this->oDB);\r
86         echo 'Postgres version found: '.$fPostgresVersion."\n";\r
87 \r
88         if ($fPostgresVersion < 9.1) {\r
89             fail('Minimum supported version of Postgresql is 9.1.');\r
90         }\r
91 \r
92         $this->pgsqlRunScript('CREATE EXTENSION IF NOT EXISTS hstore');\r
93         $this->pgsqlRunScript('CREATE EXTENSION IF NOT EXISTS postgis');\r
94 \r
95         // For extratags and namedetails the hstore_to_json converter is\r
96         // needed which is only available from Postgresql 9.3+. For older\r
97         // versions add a dummy function that returns nothing.\r
98         $iNumFunc = chksql($this->oDB->getOne("select count(*) from pg_proc where proname = 'hstore_to_json'"));\r
99 \r
100         if ($iNumFunc == 0) {\r
101             $this->pgsqlRunScript("create function hstore_to_json(dummy hstore) returns text AS 'select null::text' language sql immutable");\r
102             warn('Postgresql is too old. extratags and namedetails API not available.');\r
103         }\r
104 \r
105 \r
106         $fPostgisVersion = getPostgisVersion($this->oDB);\r
107         echo 'Postgis version found: '.$fPostgisVersion."\n";\r
108 \r
109         if ($fPostgisVersion < 2.1) {\r
110             // Functions were renamed in 2.1 and throw an annoying deprecation warning\r
111             $this->pgsqlRunScript('ALTER FUNCTION st_line_interpolate_point(geometry, double precision) RENAME TO ST_LineInterpolatePoint');\r
112             $this->pgsqlRunScript('ALTER FUNCTION ST_Line_Locate_Point(geometry, geometry) RENAME TO ST_LineLocatePoint');\r
113         }\r
114         if ($fPostgisVersion < 2.2) {\r
115             $this->pgsqlRunScript('ALTER FUNCTION ST_Distance_Spheroid(geometry, geometry, spheroid) RENAME TO ST_DistanceSpheroid');\r
116         }\r
117 \r
118         $i = chksql($this->oDB->getOne("select count(*) from pg_user where usename = '".CONST_Database_Web_User."'"));\r
119         if ($i == 0) {\r
120             echo "\nERROR: Web user '".CONST_Database_Web_User."' does not exist. Create it with:\n";\r
121             echo "\n          createuser ".CONST_Database_Web_User."\n\n";\r
122             exit(1);\r
123         }\r
124 \r
125         if (!file_exists(CONST_ExtraDataPath.'/country_osm_grid.sql.gz')) {\r
126             echo 'Error: you need to download the country_osm_grid first:';\r
127             echo "\n    wget -O ".CONST_ExtraDataPath."/country_osm_grid.sql.gz https://www.nominatim.org/data/country_grid.sql.gz\n";\r
128             exit(1);\r
129         }\r
130         $this->pgsqlRunScriptFile(CONST_BasePath.'/data/country_name.sql');\r
131         $this->pgsqlRunScriptFile(CONST_BasePath.'/data/country_naturalearthdata.sql');\r
132         $this->pgsqlRunScriptFile(CONST_BasePath.'/data/country_osm_grid.sql.gz');\r
133         $this->pgsqlRunScriptFile(CONST_BasePath.'/data/gb_postcode_table.sql');\r
134 \r
135 \r
136         if (file_exists(CONST_BasePath.'/data/gb_postcode_data.sql.gz')) {\r
137             $this->pgsqlRunScriptFile(CONST_BasePath.'/data/gb_postcode_data.sql.gz');\r
138         } else {\r
139             warn('external UK postcode table not found.');\r
140         }\r
141 \r
142         if (CONST_Use_Extra_US_Postcodes) {\r
143             $this->pgsqlRunScriptFile(CONST_BasePath.'/data/us_postcode.sql');\r
144         }\r
145 \r
146         if ($this->bNoPartitions) {\r
147             $this->pgsqlRunScript('update country_name set partition = 0');\r
148         }\r
149 \r
150         // the following will be needed by create_functions later but\r
151         // is only defined in the subsequently called T\r
152         // Create dummies here that will be overwritten by the proper\r
153         // versions in create-tables.\r
154         $this->pgsqlRunScript('CREATE TABLE IF NOT EXISTS place_boundingbox ()');\r
155         $this->pgsqlRunScript('CREATE TYPE wikipedia_article_match AS ()', false);\r
156     }\r
157 \r
158     public function importData($sOSMFile) {\r
159         info('Import data');\r
160 \r
161         $osm2pgsql = CONST_Osm2pgsql_Binary;\r
162         if (!file_exists($osm2pgsql)) {\r
163             echo "Check CONST_Osm2pgsql_Binary in your local settings file.\n";\r
164             echo "Normally you should not need to set this manually.\n";\r
165             fail("osm2pgsql not found in '$osm2pgsql'");\r
166         }\r
167 \r
168 \r
169 \r
170         if (!is_null(CONST_Osm2pgsql_Flatnode_File) && CONST_Osm2pgsql_Flatnode_File) {\r
171             $osm2pgsql .= ' --flat-nodes '.CONST_Osm2pgsql_Flatnode_File;\r
172         }\r
173 \r
174         if (CONST_Tablespace_Osm2pgsql_Data)\r
175             $osm2pgsql .= ' --tablespace-slim-data '.CONST_Tablespace_Osm2pgsql_Data;\r
176         if (CONST_Tablespace_Osm2pgsql_Index)\r
177             $osm2pgsql .= ' --tablespace-slim-index '.CONST_Tablespace_Osm2pgsql_Index;\r
178         if (CONST_Tablespace_Place_Data)\r
179             $osm2pgsql .= ' --tablespace-main-data '.CONST_Tablespace_Place_Data;\r
180         if (CONST_Tablespace_Place_Index)\r
181             $osm2pgsql .= ' --tablespace-main-index '.CONST_Tablespace_Place_Index;\r
182         $osm2pgsql .= ' -lsc -O gazetteer --hstore --number-processes 1';\r
183         $osm2pgsql .= ' -C '.$this->iCacheMemory;\r
184         $osm2pgsql .= ' -P '.$this->aDSNInfo['port'];\r
185         if (isset($this->aDSNInfo['username']) && $this->aDSNInfo['username']) {\r
186             $osm2pgsql .= ' -U ' . $this->aDSNInfo['username'];\r
187         }\r
188         if (isset($this->aDSNInfo['hostspec']) && $this->aDSNInfo['hostspec']) {\r
189             $osm2pgsql .= ' -H ' . $this->aDSNInfo['hostspec'];\r
190         }\r
191         $aProcEnv = null;\r
192         if (isset($this->aDSNInfo['password']) && $this->aDSNInfo['password']) {\r
193             $aProcEnv = array_merge(array('PGPASSWORD' => $this->aDSNInfo['password']), $_ENV);\r
194         }\r
195         $osm2pgsql .= ' -d '.$this->aDSNInfo['database'].' '.$sOSMFile;\r
196         runWithEnv($osm2pgsql, $aProcEnv);\r
197         if ($this->oDB == null) $this->oDB =& getDB();\r
198         if (!$this->sIgnoreErrors && !chksql($this->oDB->getRow('select * from place limit 1'))) {\r
199             fail('No Data');\r
200         }\r
201     }\r
202 \r
203     public function createFunctions() {\r
204         info('Create Functions');\r
205 \r
206         $this->createSqlFunctions();\r
207     }\r
208 \r
209     public function createTables() {\r
210         info('Create Tables');\r
211 \r
212         $sTemplate = file_get_contents(CONST_BasePath.'/sql/tables.sql');\r
213         $sTemplate = str_replace('{www-user}', CONST_Database_Web_User, $sTemplate);\r
214         $sTemplate = $this->replaceTablespace(\r
215             '{ts:address-data}',\r
216             CONST_Tablespace_Address_Data,\r
217             $sTemplate\r
218         );\r
219         $sTemplate = $this->replaceTablespace(\r
220             '{ts:address-index}',\r
221             CONST_Tablespace_Address_Index,\r
222             $sTemplate\r
223         );\r
224         $sTemplate = $this->replaceTablespace(\r
225             '{ts:search-data}',\r
226             CONST_Tablespace_Search_Data,\r
227             $sTemplate\r
228         );\r
229         $sTemplate = $this->replaceTablespace(\r
230             '{ts:search-index}',\r
231             CONST_Tablespace_Search_Index,\r
232             $sTemplate\r
233         );\r
234         $sTemplate = $this->replaceTablespace(\r
235             '{ts:aux-data}',\r
236             CONST_Tablespace_Aux_Data,\r
237             $sTemplate\r
238         );\r
239         $sTemplate = $this->replaceTablespace(\r
240             '{ts:aux-index}',\r
241             CONST_Tablespace_Aux_Index,\r
242             $sTemplate\r
243         );\r
244 \r
245         $this->pgsqlRunScript($sTemplate, false);\r
246 \r
247         // re-run the functions\r
248         info('Recreate Functions');\r
249         $this->createSqlFunctions();\r
250     }\r
251 \r
252     public function createPartitionTables() {\r
253         info('Create Partition Tables');\r
254 \r
255         $sTemplate = file_get_contents(CONST_BasePath.'/sql/partition-tables.src.sql');\r
256         $sTemplate = $this->replaceTablespace(\r
257             '{ts:address-data}',\r
258             CONST_Tablespace_Address_Data,\r
259             $sTemplate\r
260         );\r
261 \r
262         $sTemplate = $this->replaceTablespace(\r
263             '{ts:address-index}',\r
264             CONST_Tablespace_Address_Index,\r
265             $sTemplate\r
266         );\r
267 \r
268         $sTemplate = $this->replaceTablespace(\r
269             '{ts:search-data}',\r
270             CONST_Tablespace_Search_Data,\r
271             $sTemplate\r
272         );\r
273 \r
274         $sTemplate = $this->replaceTablespace(\r
275             '{ts:search-index}',\r
276             CONST_Tablespace_Search_Index,\r
277             $sTemplate\r
278         );\r
279 \r
280         $sTemplate = $this->replaceTablespace(\r
281             '{ts:aux-data}',\r
282             CONST_Tablespace_Aux_Data,\r
283             $sTemplate\r
284         );\r
285 \r
286         $sTemplate = $this->replaceTablespace(\r
287             '{ts:aux-index}',\r
288             CONST_Tablespace_Aux_Index,\r
289             $sTemplate\r
290         );\r
291 \r
292         $this->pgsqlRunPartitionScript($sTemplate);\r
293     }\r
294 \r
295     public function createPartitionFunctions() {\r
296         info('Create Partition Functions');\r
297 \r
298         $sTemplate = file_get_contents(CONST_BasePath.'/sql/partition-functions.src.sql');\r
299         $this->pgsqlRunPartitionScript($sTemplate);\r
300     }\r
301 \r
302     public function importWikipediaArticles() {\r
303         $sWikiArticlesFile = CONST_Wikipedia_Data_Path.'/wikipedia_article.sql.bin';\r
304         $sWikiRedirectsFile = CONST_Wikipedia_Data_Path.'/wikipedia_redirect.sql.bin';\r
305         if (file_exists($sWikiArticlesFile)) {\r
306             info('Importing wikipedia articles');\r
307             $this->pgsqlRunDropAndRestore($sWikiArticlesFile);\r
308         } else {\r
309             warn('wikipedia article dump file not found - places will have default importance');\r
310         }\r
311         if (file_exists($sWikiRedirectsFile)) {\r
312             info('Importing wikipedia redirects');\r
313             $this->pgsqlRunDropAndRestore($sWikiRedirectsFile);\r
314         } else {\r
315             warn('wikipedia redirect dump file not found - some place importance values may be missing');\r
316         }\r
317         echo ' finish wikipedia';\r
318     }    \r
319 \r
320     public function loadData($bDisableTokenPrecalc) {\r
321         info('Drop old Data');\r
322 \r
323         if ($this->oDB == null) $this->oDB =& getDB();\r
324 \r
325         if (!pg_query($this->oDB->connection, 'TRUNCATE word')) fail(pg_last_error($this->oDB->connection));\r
326         echo '.';\r
327         if (!pg_query($this->oDB->connection, 'TRUNCATE placex')) fail(pg_last_error($this->oDB->connection));\r
328         echo '.';\r
329         if (!pg_query($this->oDB->connection, 'TRUNCATE location_property_osmline')) fail(pg_last_error($this->oDB->connection));\r
330         echo '.';\r
331         if (!pg_query($this->oDB->connection, 'TRUNCATE place_addressline')) fail(pg_last_error($this->oDB->connection));\r
332         echo '.';\r
333         if (!pg_query($this->oDB->connection, 'TRUNCATE place_boundingbox')) fail(pg_last_error($this->oDB->connection));\r
334         echo '.';\r
335         if (!pg_query($this->oDB->connection, 'TRUNCATE location_area')) fail(pg_last_error($this->oDB->connection));\r
336         echo '.';\r
337         if (!pg_query($this->oDB->connection, 'TRUNCATE search_name')) fail(pg_last_error($this->oDB->connection));\r
338         echo '.';\r
339         if (!pg_query($this->oDB->connection, 'TRUNCATE search_name_blank')) fail(pg_last_error($this->oDB->connection));\r
340         echo '.';\r
341         if (!pg_query($this->oDB->connection, 'DROP SEQUENCE seq_place')) fail(pg_last_error($this->oDB->connection));\r
342         echo '.';\r
343         if (!pg_query($this->oDB->connection, 'CREATE SEQUENCE seq_place start 100000')) fail(pg_last_error($this->oDB->connection));\r
344         echo '.';\r
345 \r
346         $sSQL = 'select distinct partition from country_name';\r
347         $aPartitions = chksql($this->oDB->getCol($sSQL));\r
348         if (!$this->bNoPartitions) $aPartitions[] = 0;\r
349         foreach ($aPartitions as $sPartition) {\r
350             if (!pg_query($this->oDB->connection, 'TRUNCATE location_road_'.$sPartition)) fail(pg_last_error($this->oDB->connection));\r
351             echo '.';\r
352         }\r
353 \r
354         // used by getorcreate_word_id to ignore frequent partial words\r
355         $sSQL = 'CREATE OR REPLACE FUNCTION get_maxwordfreq() RETURNS integer AS ';\r
356         $sSQL .= '$$ SELECT '.CONST_Max_Word_Frequency.' as maxwordfreq; $$ LANGUAGE SQL IMMUTABLE';\r
357         if (!pg_query($this->oDB->connection, $sSQL)) {\r
358             fail(pg_last_error($this->oDB->connection));\r
359         }\r
360         echo ".\n";\r
361 \r
362         // pre-create the word list\r
363         if (!$bDisableTokenPrecalc) {\r
364             info('Loading word list');\r
365             $this->pgsqlRunScriptFile(CONST_BasePath.'/data/words.sql');\r
366         }\r
367 \r
368         info('Load Data');\r
369         $sColumns = 'osm_type, osm_id, class, type, name, admin_level, address, extratags, geometry';\r
370         $aDBInstances = array();\r
371         $iLoadThreads = max(1, $this->iInstances - 1);\r
372         for ($i = 0; $i < $iLoadThreads; $i++) {\r
373             $aDBInstances[$i] =& getDB(true);\r
374             $sSQL = "INSERT INTO placex ($sColumns) SELECT $sColumns FROM place WHERE osm_id % $iLoadThreads = $i";\r
375             $sSQL .= " and not (class='place' and type='houses' and osm_type='W'";\r
376             $sSQL .= "          and ST_GeometryType(geometry) = 'ST_LineString')";\r
377             $sSQL .= ' and ST_IsValid(geometry)';\r
378             if ($this->sVerbose) echo "$sSQL\n";\r
379             if (!pg_send_query($aDBInstances[$i]->connection, $sSQL)) {\r
380                 fail(pg_last_error($aDBInstances[$i]->connection));\r
381             }\r
382         }\r
383 \r
384         // last thread for interpolation lines\r
385 \r
386         $aDBInstances[$iLoadThreads] =& getDB(true);\r
387         $sSQL = 'insert into location_property_osmline';\r
388         $sSQL .= ' (osm_id, address, linegeo)';\r
389         $sSQL .= ' SELECT osm_id, address, geometry from place where ';\r
390         $sSQL .= "class='place' and type='houses' and osm_type='W' and ST_GeometryType(geometry) = 'ST_LineString'";\r
391         if ($this->sVerbose) echo "$sSQL\n";\r
392         if (!pg_send_query($aDBInstances[$iLoadThreads]->connection, $sSQL)) {\r
393             fail(pg_last_error($aDBInstances[$iLoadThreads]->connection));\r
394         }\r
395 \r
396         $bFailed = false;\r
397         for ($i = 0; $i <= $iLoadThreads; $i++) {\r
398             while (($hPGresult = pg_get_result($aDBInstances[$i]->connection)) !== false) {\r
399                 $resultStatus = pg_result_status($hPGresult);\r
400                 // PGSQL_EMPTY_QUERY, PGSQL_COMMAND_OK, PGSQL_TUPLES_OK,\r
401                 // PGSQL_COPY_OUT, PGSQL_COPY_IN, PGSQL_BAD_RESPONSE,\r
402                 // PGSQL_NONFATAL_ERROR and PGSQL_FATAL_ERROR\r
403                 echo 'Query result ' . $i . ' is: ' . $resultStatus . "\n";\r
404                 if ($resultStatus != PGSQL_COMMAND_OK && $resultStatus != PGSQL_TUPLES_OK) {\r
405                     $resultError = pg_result_error($hPGresult);\r
406                     echo '-- error text ' . $i . ': ' . $resultError . "\n";\r
407                     $bFailed = true;\r
408                 }\r
409             }\r
410         }\r
411         if ($bFailed) {\r
412             fail('SQL errors loading placex and/or location_property_osmline tables');\r
413         }\r
414         echo "\n";\r
415         info('Reanalysing database');\r
416         $this->pgsqlRunScript('ANALYSE');\r
417 \r
418         $sDatabaseDate = getDatabaseDate($this->oDB);\r
419         pg_query($this->oDB->connection, 'TRUNCATE import_status');\r
420         if ($sDatabaseDate === false) {\r
421             warn('could not determine database date.');\r
422         } else {\r
423             $sSQL = "INSERT INTO import_status (lastimportdate) VALUES('".$sDatabaseDate."')";\r
424             pg_query($this->oDB->connection, $sSQL);\r
425             echo "Latest data imported from $sDatabaseDate.\n";\r
426         }\r
427     }\r
428 \r
429     public function importTigerData() {\r
430         info('Import Tiger data');\r
431 \r
432         $sTemplate = file_get_contents(CONST_BasePath.'/sql/tiger_import_start.sql');\r
433         $sTemplate = str_replace('{www-user}', CONST_Database_Web_User, $sTemplate);\r
434         $sTemplate = $this->replaceTablespace(\r
435             '{ts:aux-data}',\r
436             CONST_Tablespace_Aux_Data,\r
437             $sTemplate\r
438         );\r
439         $sTemplate = $this->replaceTablespace(\r
440             '{ts:aux-index}',\r
441             CONST_Tablespace_Aux_Index,\r
442             $sTemplate\r
443         );\r
444         $this->pgsqlRunScript($sTemplate, false);\r
445 \r
446         $aDBInstances = array();\r
447         for ($i = 0; $i < $this->iInstances; $i++) {\r
448             $aDBInstances[$i] =& getDB(true);\r
449         }\r
450 \r
451         foreach (glob(CONST_Tiger_Data_Path.'/*.sql') as $sFile) {\r
452             echo $sFile.': ';\r
453             $hFile = fopen($sFile, 'r');\r
454             $sSQL = fgets($hFile, 100000);\r
455             $iLines = 0;\r
456             while (true) {\r
457                 for ($i = 0; $i < $this->iInstances; $i++) {\r
458                     if (!pg_connection_busy($aDBInstances[$i]->connection)) {\r
459                         while (pg_get_result($aDBInstances[$i]->connection));\r
460                         $sSQL = fgets($hFile, 100000);\r
461                         if (!$sSQL) break 2;\r
462                         if (!pg_send_query($aDBInstances[$i]->connection, $sSQL)) fail(pg_last_error($this->oDB->connection));\r
463                         $iLines++;\r
464                         if ($iLines == 1000) {\r
465                             echo '.';\r
466                             $iLines = 0;\r
467                         }\r
468                     }\r
469                 }\r
470                 usleep(10);\r
471             }\r
472             fclose($hFile);\r
473 \r
474             $bAnyBusy = true;\r
475             while ($bAnyBusy) {\r
476                 $bAnyBusy = false;\r
477                 for ($i = 0; $i < $this->iInstances; $i++) {\r
478                     if (pg_connection_busy($aDBInstances[$i]->connection)) $bAnyBusy = true;\r
479                 }\r
480                 usleep(10);\r
481             }\r
482             echo "\n";\r
483         }\r
484 \r
485         info('Creating indexes on Tiger data');\r
486         $sTemplate = file_get_contents(CONST_BasePath.'/sql/tiger_import_finish.sql');\r
487         $sTemplate = str_replace('{www-user}', CONST_Database_Web_User, $sTemplate);\r
488         $sTemplate = $this->replaceTablespace(\r
489             '{ts:aux-data}',\r
490             CONST_Tablespace_Aux_Data,\r
491             $sTemplate\r
492         );\r
493         $sTemplate = $this->replaceTablespace(\r
494             '{ts:aux-index}',\r
495             CONST_Tablespace_Aux_Index,\r
496             $sTemplate\r
497         );\r
498         $this->pgsqlRunScript($sTemplate, false);\r
499     }\r
500 \r
501     public function calculatePostcodes($bCMDResultAll) {\r
502         info('Calculate Postcodes');\r
503         if ($this->oDB == null) $this->oDB =& getDB();\r
504         if (!pg_query($this->oDB->connection, 'TRUNCATE location_postcode')) {\r
505             fail(pg_last_error($this->oDB->connection));\r
506         }\r
507 \r
508 \r
509         $sSQL  = 'INSERT INTO location_postcode';\r
510         $sSQL .= ' (place_id, indexed_status, country_code, postcode, geometry) ';\r
511         $sSQL .= "SELECT nextval('seq_place'), 1, country_code,";\r
512         $sSQL .= "       upper(trim (both ' ' from address->'postcode')) as pc,";\r
513         $sSQL .= '       ST_Centroid(ST_Collect(ST_Centroid(geometry)))';\r
514         $sSQL .= '  FROM placex';\r
515         $sSQL .= " WHERE address ? 'postcode' AND address->'postcode' NOT SIMILAR TO '%(,|;)%'";\r
516         $sSQL .= '       AND geometry IS NOT null';\r
517         $sSQL .= ' GROUP BY country_code, pc';\r
518 \r
519         if (!pg_query($this->oDB->connection, $sSQL)) {\r
520             fail(pg_last_error($this->oDB->connection));\r
521         }\r
522 \r
523         if (CONST_Use_Extra_US_Postcodes) {\r
524             // only add postcodes that are not yet available in OSM\r
525             $sSQL  = 'INSERT INTO location_postcode';\r
526             $sSQL .= ' (place_id, indexed_status, country_code, postcode, geometry) ';\r
527             $sSQL .= "SELECT nextval('seq_place'), 1, 'us', postcode,";\r
528             $sSQL .= '       ST_SetSRID(ST_Point(x,y),4326)';\r
529             $sSQL .= '  FROM us_postcode WHERE postcode NOT IN';\r
530             $sSQL .= '        (SELECT postcode FROM location_postcode';\r
531             $sSQL .= "          WHERE country_code = 'us')";\r
532             if (!pg_query($this->oDB->connection, $sSQL)) fail(pg_last_error($this->oDB->connection));\r
533         }\r
534 \r
535         // add missing postcodes for GB (if available)\r
536         $sSQL  = 'INSERT INTO location_postcode';\r
537         $sSQL .= ' (place_id, indexed_status, country_code, postcode, geometry) ';\r
538         $sSQL .= "SELECT nextval('seq_place'), 1, 'gb', postcode, geometry";\r
539         $sSQL .= '  FROM gb_postcode WHERE postcode NOT IN';\r
540         $sSQL .= '           (SELECT postcode FROM location_postcode';\r
541         $sSQL .= "             WHERE country_code = 'gb')";\r
542         if (!pg_query($this->oDB->connection, $sSQL)) fail(pg_last_error($this->oDB->connection));\r
543 \r
544         if (!$bCMDResultAll) {\r
545             $sSQL = "DELETE FROM word WHERE class='place' and type='postcode'";\r
546             $sSQL .= 'and word NOT IN (SELECT postcode FROM location_postcode)';\r
547             if (!pg_query($this->oDB->connection, $sSQL)) {\r
548                 fail(pg_last_error($this->oDB->connection));\r
549             }\r
550         }\r
551         $sSQL = 'SELECT count(getorcreate_postcode_id(v)) FROM ';\r
552         $sSQL .= '(SELECT distinct(postcode) as v FROM location_postcode) p';\r
553 \r
554         if (!pg_query($this->oDB->connection, $sSQL)) {\r
555             fail(pg_last_error($this->oDB->connection));\r
556         }\r
557     }\r
558 \r
559     public function index($bIndexNoanalyse) {\r
560         $sOutputFile = '';\r
561         $sBaseCmd = CONST_InstallPath.'/nominatim/nominatim -i -d '.$this->aDSNInfo['database'].' -P '.$this->aDSNInfo['port'].' -t '.$this->iInstances.$sOutputFile;\r
562         if (isset($this->aDSNInfo['hostspec']) && $this->aDSNInfo['hostspec']) {\r
563             $sBaseCmd .= ' -H ' . $this->aDSNInfo['hostspec'];\r
564         }\r
565         if (isset($this->aDSNInfo['username']) && $this->aDSNInfo['username']) {\r
566             $sBaseCmd .= ' -U ' . $this->aDSNInfo['username'];\r
567         }\r
568         $aProcEnv = null;\r
569         if (isset($this->aDSNInfo['password']) && $this->aDSNInfo['password']) {\r
570             $aProcEnv = array_merge(array('PGPASSWORD' => $this->aDSNInfo['password']), $_ENV);\r
571         }\r
572 \r
573         info('Index ranks 0 - 4');\r
574         $iStatus = runWithEnv($sBaseCmd.' -R 4', $aProcEnv);\r
575         if ($iStatus != 0) {\r
576             fail('error status ' . $iStatus . ' running nominatim!');\r
577         }\r
578         if (!$bIndexNoanalyse) $this->pgsqlRunScript('ANALYSE');\r
579         info('Index ranks 5 - 25');\r
580         $iStatus = runWithEnv($sBaseCmd.' -r 5 -R 25', $aProcEnv);\r
581         if ($iStatus != 0) {\r
582             fail('error status ' . $iStatus . ' running nominatim!');\r
583         }\r
584         if (!$bIndexNoanalyse) $this->pgsqlRunScript('ANALYSE');\r
585         info('Index ranks 26 - 30');\r
586         $iStatus = runWithEnv($sBaseCmd.' -r 26', $aProcEnv);\r
587         if ($iStatus != 0) {\r
588             fail('error status ' . $iStatus . ' running nominatim!');\r
589         }\r
590         info('Index postcodes');\r
591         if ($this->oDB == null) $this->oDB =& getDB();\r
592         $sSQL = 'UPDATE location_postcode SET indexed_status = 0';\r
593         if (!pg_query($this->oDB->connection, $sSQL)) fail(pg_last_error($this->oDB->connection));\r
594     }    \r
595 \r
596     public function createSearchIndices() {\r
597         info('Create Search indices');\r
598 \r
599         $sTemplate = file_get_contents(CONST_BasePath.'/sql/indices.src.sql');\r
600         $sTemplate = str_replace('{www-user}', CONST_Database_Web_User, $sTemplate);\r
601         $sTemplate = $this->replaceTablespace(\r
602             '{ts:address-index}',\r
603             CONST_Tablespace_Address_Index,\r
604             $sTemplate\r
605         );\r
606         $sTemplate = $this->replaceTablespace(\r
607             '{ts:search-index}',\r
608             CONST_Tablespace_Search_Index,\r
609             $sTemplate\r
610         );\r
611         $sTemplate = $this->replaceTablespace(\r
612             '{ts:aux-index}',\r
613             CONST_Tablespace_Aux_Index,\r
614             $sTemplate\r
615         );\r
616         $this->pgsqlRunScript($sTemplate);\r
617     }\r
618 \r
619     public function createCountryNames() {\r
620         info('Create search index for default country names');\r
621 \r
622         $this->pgsqlRunScript("select getorcreate_country(make_standard_name('uk'), 'gb')");\r
623         $this->pgsqlRunScript("select getorcreate_country(make_standard_name('united states'), 'us')");\r
624         $this->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');\r
625         $this->pgsqlRunScript("select count(*) from (select getorcreate_country(make_standard_name(name->'name'), country_code) from country_name where name ? 'name') as x");\r
626         $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 ';\r
627         if (CONST_Languages) {\r
628             $sSQL .= 'in ';\r
629             $sDelim = '(';\r
630             foreach (explode(',', CONST_Languages) as $sLang) {\r
631                 $sSQL .= $sDelim."'name:$sLang'";\r
632                 $sDelim = ',';\r
633             }\r
634             $sSQL .= ')';\r
635         } else {\r
636             // all include all simple name tags\r
637             $sSQL .= "like 'name:%'";\r
638         }\r
639         $sSQL .= ') v';\r
640         $this->pgsqlRunScript($sSQL);\r
641     }\r
642 \r
643     public function drop() {\r
644         info('Drop tables only required for updates');\r
645 \r
646         // The implementation is potentially a bit dangerous because it uses\r
647         // a positive selection of tables to keep, and deletes everything else.\r
648         // Including any tables that the unsuspecting user might have manually\r
649         // created. USE AT YOUR OWN PERIL.\r
650         // tables we want to keep. everything else goes.\r
651         $aKeepTables = array(\r
652                         '*columns',\r
653                         'import_polygon_*',\r
654                         'import_status',\r
655                         'place_addressline',\r
656                         'location_postcode',\r
657                         'location_property*',\r
658                         'placex',\r
659                         'search_name',\r
660                         'seq_*',\r
661                         'word',\r
662                         'query_log',\r
663                         'new_query_log',\r
664                         'spatial_ref_sys',\r
665                         'country_name',\r
666                         'place_classtype_*'\r
667                        );\r
668 \r
669         if ($this->oDB = null) $this->oDB =& getDB();\r
670         $aDropTables = array();\r
671         $aHaveTables = chksql($this->oDB->getCol("SELECT tablename FROM pg_tables WHERE schemaname='public'"));\r
672 \r
673         foreach ($aHaveTables as $sTable) {\r
674             $bFound = false;\r
675             foreach ($aKeepTables as $sKeep) {\r
676                 if (fnmatch($sKeep, $sTable)) {\r
677                     $bFound = true;\r
678                     break;\r
679                 }\r
680             }\r
681             if (!$bFound) array_push($aDropTables, $sTable);\r
682         }\r
683         foreach ($aDropTables as $sDrop) {\r
684             if ($this->sVerbose) echo "dropping table $sDrop\n";\r
685             @pg_query($this->oDB->connection, "DROP TABLE $sDrop CASCADE");\r
686             // ignore warnings/errors as they might be caused by a table having\r
687             // been deleted already by CASCADE\r
688         }\r
689 \r
690         if (!is_null(CONST_Osm2pgsql_Flatnode_File) && CONST_Osm2pgsql_Flatnode_File) {\r
691             if ($sVerbose) echo 'deleting '.CONST_Osm2pgsql_Flatnode_File."\n";\r
692             unlink(CONST_Osm2pgsql_Flatnode_File);\r
693         }\r
694     }\r
695 \r
696     private function pgsqlRunDropAndRestore($sDumpFile) {\r
697         if (!isset($this->aDSNInfo['port']) || !$this->aDSNInfo['port']) $this->aDSNInfo['port'] = 5432;\r
698         $sCMD = 'pg_restore -p '.$this->aDSNInfo['port'].' -d '.$this->aDSNInfo['database'].' -Fc --clean '.$sDumpFile;\r
699         if (isset($this->aDSNInfo['hostspec']) && $this->aDSNInfo['hostspec']) {\r
700             $sCMD .= ' -h ' . $this->aDSNInfo['hostspec'];\r
701         }\r
702         if (isset($this->aDSNInfo['username']) && $this->aDSNInfo['username']) {\r
703             $sCMD .= ' -U ' . $this->aDSNInfo['username'];\r
704         }\r
705         $aProcEnv = null;\r
706         if (isset($this->aDSNInfo['password']) && $this->aDSNInfo['password']) {\r
707             $aProcEnv = array_merge(array('PGPASSWORD' => $this->aDSNInfo['password']), $_ENV);\r
708         }\r
709         $iReturn = runWithEnv($sCMD, $aProcEnv);    // /lib/cmd.php "function runWithEnv($sCmd, $aEnv)"\r
710     }\r
711                      \r
712     private function pgsqlRunScript($sScript, $bfatal = true) {\r
713         runSQLScript(\r
714             $sScript,\r
715             $bfatal,\r
716             $this->sVerbose,\r
717             $this->sIgnoreErrors\r
718         );\r
719     }\r
720 \r
721     private function createSqlFunctions() {        \r
722         $sTemplate = file_get_contents(CONST_BasePath.'/sql/functions.sql');\r
723         $sTemplate = str_replace('{modulepath}', $this->sModulePath, $sTemplate);\r
724         if ($this->bEnableDiffUpdates) {\r
725             $sTemplate = str_replace('RETURN NEW; -- %DIFFUPDATES%', '--', $sTemplate);\r
726         }\r
727         if ($this->bEnableDebugStatements) {\r
728             $sTemplate = str_replace('--DEBUG:', '', $sTemplate);\r
729         }\r
730         if (CONST_Limit_Reindexing) {\r
731             $sTemplate = str_replace('--LIMIT INDEXING:', '', $sTemplate);\r
732         }\r
733         if (!CONST_Use_US_Tiger_Data) {\r
734             $sTemplate = str_replace('-- %NOTIGERDATA% ', '', $sTemplate);\r
735         }\r
736         if (!CONST_Use_Aux_Location_data) {\r
737             $sTemplate = str_replace('-- %NOAUXDATA% ', '', $sTemplate);\r
738         }\r
739         $this->pgsqlRunScript($sTemplate);\r
740     }\r
741 \r
742     private function pgsqlRunPartitionScript($sTemplate) {\r
743         if ($this->oDB == null) $this->oDB =& getDB();\r
744 \r
745         $sSQL = 'select distinct partition from country_name';\r
746         $aPartitions = chksql($this->oDB->getCol($sSQL));\r
747         if (!$this->bNoPartitions) $aPartitions[] = 0;\r
748 \r
749         preg_match_all('#^-- start(.*?)^-- end#ms', $sTemplate, $aMatches, PREG_SET_ORDER);\r
750         foreach ($aMatches as $aMatch) {\r
751             $sResult = '';\r
752             foreach ($aPartitions as $sPartitionName) {\r
753                 $sResult .= str_replace('-partition-', $sPartitionName, $aMatch[1]);\r
754             }\r
755             $sTemplate = str_replace($aMatch[0], $sResult, $sTemplate);\r
756         }\r
757 \r
758         $this->pgsqlRunScript($sTemplate);\r
759     }  \r
760 \r
761     private function pgsqlRunScriptFile($sFilename) {\r
762         if (!file_exists($sFilename)) fail('unable to find '.$sFilename);\r
763 \r
764         $sCMD = 'psql -p '.$this->aDSNInfo['port'].' -d '.$this->aDSNInfo['database'];\r
765         if (!$this->sVerbose) {\r
766             $sCMD .= ' -q';\r
767         }\r
768         if (isset($this->aDSNInfo['hostspec']) && $this->aDSNInfo['hostspec']) {\r
769             $sCMD .= ' -h ' . $this->aDSNInfo['hostspec'];\r
770         }\r
771         if (isset($this->aDSNInfo['username']) && $this->aDSNInfo['username']) {\r
772             $sCMD .= ' -U ' . $this->aDSNInfo['username'];\r
773         }\r
774         $aProcEnv = null;\r
775         if (isset($this->aDSNInfo['password']) && $this->aDSNInfo['password']) {\r
776             $aProcEnv = array_merge(array('PGPASSWORD' => $this->aDSNInfo['password']), $_ENV);\r
777         }\r
778         $ahGzipPipes = null;\r
779         if (preg_match('/\\.gz$/', $sFilename)) {\r
780             $aDescriptors = array(\r
781                              0 => array('pipe', 'r'),\r
782                              1 => array('pipe', 'w'),\r
783                              2 => array('file', '/dev/null', 'a')\r
784                             );\r
785             $hGzipProcess = proc_open('zcat '.$sFilename, $aDescriptors, $ahGzipPipes);\r
786             if (!is_resource($hGzipProcess)) fail('unable to start zcat');\r
787             $aReadPipe = $ahGzipPipes[1];\r
788             fclose($ahGzipPipes[0]);\r
789         } else {\r
790             $sCMD .= ' -f '.$sFilename;\r
791             $aReadPipe = array('pipe', 'r');\r
792         }\r
793         $aDescriptors = array(\r
794                          0 => $aReadPipe,\r
795                          1 => array('pipe', 'w'),\r
796                          2 => array('file', '/dev/null', 'a')\r
797                         );\r
798         $ahPipes = null;\r
799         $hProcess = proc_open($sCMD, $aDescriptors, $ahPipes, null, $aProcEnv);\r
800         if (!is_resource($hProcess)) fail('unable to start pgsql');\r
801         // TODO: error checking\r
802         while (!feof($ahPipes[1])) {\r
803             echo fread($ahPipes[1], 4096);\r
804         }\r
805         fclose($ahPipes[1]);\r
806         $iReturn = proc_close($hProcess);\r
807         if ($iReturn > 0) {\r
808             fail("pgsql returned with error code ($iReturn)");\r
809         }\r
810         if ($ahGzipPipes) {\r
811             fclose($ahGzipPipes[1]);\r
812             proc_close($hGzipProcess);\r
813         }\r
814     }\r
815     private function replaceTablespace($sTemplate, $sTablespace, $sSql) {\r
816         if ($sTablespace) {\r
817             $sSql = str_replace($sTemplate, 'TABLESPACE "'.$sTablespace.'"', $sSql);\r
818         } else {\r
819             $sSql = str_replace($sTemplate, '', $sSql);\r
820         }\r
821         return $sSql;\r
822     }\r
823 }\r