]> git.openstreetmap.org Git - nominatim.git/blob - lib-php/admin/export.php
decode_json() always create arrays instead of objects
[nominatim.git] / lib-php / admin / export.php
1 <?php
2 /**
3  * SPDX-License-Identifier: GPL-2.0-only
4  *
5  * This file is part of Nominatim. (https://nominatim.org)
6  *
7  * Copyright (C) 2022 by the Nominatim developer community.
8  * For a full list of authors see the git log.
9  */
10     @define('CONST_LibDir', dirname(dirname(__FILE__)));
11     // Script to extract structured city and street data
12     // from a running nominatim instance as CSV data
13
14
15     require_once(CONST_LibDir.'/init-cmd.php');
16     require_once(CONST_LibDir.'/ParameterParser.php');
17     ini_set('memory_limit', '800M');
18
19     $aCMDOptions = array(
20                     'Export addresses as CSV file from a Nominatim database',
21                     array('help', 'h', 0, 1, 0, 0, false, 'Show Help'),
22                     array('quiet', 'q', 0, 1, 0, 0, 'bool', 'Quiet output'),
23                     array('verbose', 'v', 0, 1, 0, 0, 'bool', 'Verbose output'),
24
25                     array('output-type', '', 0, 1, 1, 1, 'str', 'Type of places to output (see below)'),
26                     array('output-format', '', 0, 1, 1, 1, 'str', 'Column mapping (see below)'),
27                     array('output-all-postcodes', '', 0, 1, 0, 0, 'bool', 'List all postcodes for address instead of just the most likely one'),
28                     array('language', '', 0, 1, 1, 1, 'str', 'Preferred language for output (local name, if omitted)'),
29                     array('restrict-to-country', '', 0, 1, 1, 1, 'str', 'Export only objects within country (country code)'),
30                     array('restrict-to-osm-node', '', 0, 1, 1, 1, 'int', 'Export only objects that are children of this OSM node'),
31                     array('restrict-to-osm-way', '', 0, 1, 1, 1, 'int', 'Export only objects that are children of this OSM way'),
32                     array('restrict-to-osm-relation', '', 0, 1, 1, 1, 'int', 'Export only objects that are children of this OSM relation'),
33                     array('project-dir', '', 0, 1, 1, 1, 'realpath', 'Base directory of the Nominatim installation (default: .)'),
34                     "\nAddress ranks: continent, country, state, county, city, suburb, street, path",
35                     'Additional output types: postcode, placeid (placeid for each object)',
36                     "\noutput-format must be a semicolon-separated list of address ranks. Multiple ranks",
37                     'can be merged into one column by simply using a comma-separated list.',
38                     "\nDefault output-type: street",
39         'Default output format: street;suburb;city;county;state;country'
40                    );
41     getCmdOpt($_SERVER['argv'], $aCMDOptions, $aCMDResult, true, true);
42
43     loadSettings($aCMDResult['project-dir'] ?? getcwd());
44
45     $aRankmap = array(
46                  'continent' => 1,
47                  'country' => 4,
48                  'state' => 8,
49                  'county' => 12,
50                  'city' => 16,
51                  'suburb' => 20,
52                  'street' => 26,
53                  'path' => 27
54                 );
55
56     $oDB = new Nominatim\DB();
57     $oDB->connect();
58
59     if (isset($aCMDResult['output-type'])) {
60         if (!isset($aRankmap[$aCMDResult['output-type']])) {
61             fail('unknown output-type: '.$aCMDResult['output-type']);
62         }
63         $iOutputRank = $aRankmap[$aCMDResult['output-type']];
64     } else {
65         $iOutputRank = $aRankmap['street'];
66     }
67
68
69     // Preferred language
70     $oParams = new Nominatim\ParameterParser();
71     if (!isset($aCMDResult['language'])) {
72         $aCMDResult['language'] = 'xx';
73     }
74     $aLangPrefOrder = $oParams->getPreferredLanguages($aCMDResult['language']);
75     $sLanguagePrefArraySQL = $oDB->getArraySQL($oDB->getDBQuotedList($aLangPrefOrder));
76
77     // output formatting: build up a lookup table that maps address ranks to columns
78     $aColumnMapping = array();
79     $iNumCol = 0;
80     if (!isset($aCMDResult['output-format'])) {
81         $aCMDResult['output-format'] = 'street;suburb;city;county;state;country';
82     }
83     foreach (preg_split('/\s*;\s*/', $aCMDResult['output-format']) as $sColumn) {
84         $bHasData = false;
85         foreach (preg_split('/\s*,\s*/', $sColumn) as $sRank) {
86             if ($sRank == 'postcode' || $sRank == 'placeid') {
87                 $aColumnMapping[$sRank] = $iNumCol;
88                 $bHasData = true;
89             } elseif (isset($aRankmap[$sRank])) {
90                 $iRank = $aRankmap[$sRank];
91                 if ($iRank <= $iOutputRank) {
92                     $aColumnMapping[(string)$iRank] = $iNumCol;
93                     $bHasData = true;
94                 }
95             }
96         }
97         if ($bHasData) {
98             $iNumCol++;
99         }
100     }
101
102     // build the query for objects
103     $sPlacexSQL = 'select min(place_id) as place_id, ';
104     $sPlacexSQL .= 'array_agg(place_id) as place_ids, ';
105     $sPlacexSQL .= 'country_code as cc, ';
106     $sPlacexSQL .= 'postcode, ';
107     // get the address places excluding postcodes
108     $sPlacexSQL .= 'array(select address_place_id from place_addressline a';
109     $sPlacexSQL .= ' where a.place_id = placex.place_id and isaddress';
110     $sPlacexSQL .= '  and address_place_id != placex.place_id';
111     $sPlacexSQL .= '  and not cached_rank_address in (5,11)';
112     $sPlacexSQL .= '  and cached_rank_address > 2 order by cached_rank_address)';
113     $sPlacexSQL .= ' as address';
114     $sPlacexSQL .= ' from placex where name is not null and linked_place_id is null';
115
116     $sPlacexSQL .= ' and rank_address = '.$iOutputRank;
117
118     if (isset($aCMDResult['restrict-to-country'])) {
119         $sPlacexSQL .= ' and country_code = '.$oDB->getDBQuoted($aCMDResult['restrict-to-country']);
120     }
121
122     // restriction to parent place id
123     $sParentId = false;
124     $sOsmType = false;
125
126     if (isset($aCMDResult['restrict-to-osm-node'])) {
127         $sOsmType = 'N';
128         $sOsmId = $aCMDResult['restrict-to-osm-node'];
129     }
130     if (isset($aCMDResult['restrict-to-osm-way'])) {
131         $sOsmType = 'W';
132         $sOsmId = $aCMDResult['restrict-to-osm-way'];
133     }
134     if (isset($aCMDResult['restrict-to-osm-relation'])) {
135         $sOsmType = 'R';
136         $sOsmId = $aCMDResult['restrict-to-osm-relation'];
137     }
138     if ($sOsmType) {
139         $sSQL = 'select place_id from placex where osm_type = :osm_type and osm_id = :osm_id';
140         $sParentId = $oDB->getOne($sSQL, array('osm_type' => $sOsmType, 'osm_id' => $sOsmId));
141         if (!$sParentId) {
142             fail('Could not find place '.$sOsmType.' '.$sOsmId);
143         }
144     }
145     if ($sParentId) {
146         $sPlacexSQL .= ' and place_id in (select place_id from place_addressline where address_place_id = '.$sParentId.' and isaddress)';
147     }
148
149     $sPlacexSQL .= " group by name->'name', address, postcode, country_code, placex.place_id";
150
151     // Iterate over placeids
152     // to get further hierarchical information
153     //var_dump($sPlacexSQL);
154     $oResults = $oDB->getQueryStatement($sPlacexSQL);
155     $fOutstream = fopen('php://output', 'w');
156     while ($aRow = $oResults->fetch()) {
157         $iPlaceID = $aRow['place_id'];
158         $sSQL = "select rank_address,get_name_by_language(name,$sLanguagePrefArraySQL) as localname from get_addressdata(:place_id, -1)";
159         $sSQL .= ' WHERE isaddress';
160         $sSQL .= ' order by rank_address desc,isaddress desc';
161         $aAddressLines = $oDB->getAll($sSQL, array('place_id' => $iPlaceID));
162
163         $aOutput = array_fill(0, $iNumCol, '');
164         // output address parts
165         foreach ($aAddressLines as $aAddress) {
166             if (isset($aColumnMapping[$aAddress['rank_address']])) {
167                 $aOutput[$aColumnMapping[$aAddress['rank_address']]] = $aAddress['localname'];
168             }
169         }
170         // output postcode
171         if (isset($aColumnMapping['postcode'])) {
172             if ($aCMDResult['output-all-postcodes']) {
173                 $sSQL = 'select array_agg(px.postcode) from placex px join place_addressline pa ';
174                 $sSQL .= 'on px.place_id = pa.address_place_id ';
175                 $sSQL .= 'where pa.cached_rank_address in (5,11) ';
176                 $sSQL .= 'and pa.place_id in (select place_id from place_addressline where address_place_id in (:first_place_id)) ';
177                 $sSQL .= 'group by postcode order by count(*) desc limit 1';
178                 $sRes = $oDB->getOne($sSQL, array('first_place_id' => substr($aRow['place_ids'], 1, -1)));
179
180                 $aOutput[$aColumnMapping['postcode']] = substr($sRes, 1, -1);
181             } else {
182                 $aOutput[$aColumnMapping['postcode']] = $aRow['postcode'];
183             }
184         }
185         if (isset($aColumnMapping['placeid'])) {
186             $aOutput[$aColumnMapping['placeid']] = substr($aRow['place_ids'], 1, -1);
187         }
188         fputcsv($fOutstream, $aOutput);
189     }
190     fclose($fOutstream);