2 Command-line interface to the Nominatim functions for import, update,
3 database administration and querying.
9 from pathlib import Path
11 from .config import Configuration
12 from .tools.exec_utils import run_legacy_script, run_php_server
13 from .errors import UsageError
16 LOG = logging.getLogger()
19 class CommandlineParser:
20 """ Wraps some of the common functions for parsing the command line
21 and setting up subcommands.
23 def __init__(self, prog, description):
24 self.parser = argparse.ArgumentParser(
26 description=description,
27 formatter_class=argparse.RawDescriptionHelpFormatter)
29 self.subs = self.parser.add_subparsers(title='available commands',
32 # Arguments added to every sub-command
33 self.default_args = argparse.ArgumentParser(add_help=False)
34 group = self.default_args.add_argument_group('Default arguments')
35 group.add_argument('-h', '--help', action='help',
36 help='Show this help message and exit')
37 group.add_argument('-q', '--quiet', action='store_const', const=0,
38 dest='verbose', default=1,
39 help='Print only error messages')
40 group.add_argument('-v', '--verbose', action='count', default=1,
41 help='Increase verboseness of output')
42 group.add_argument('--project-dir', metavar='DIR', default='.',
43 help='Base directory of the Nominatim installation (default:.)')
44 group.add_argument('-j', '--threads', metavar='NUM', type=int,
45 help='Number of parallel threads to use')
48 def add_subcommand(self, name, cmd):
49 """ Add a subcommand to the parser. The subcommand must be a class
50 with a function add_args() that adds the parameters for the
51 subcommand and a run() function that executes the command.
53 parser = self.subs.add_parser(name, parents=[self.default_args],
54 help=cmd.__doc__.split('\n', 1)[0],
55 description=cmd.__doc__,
56 formatter_class=argparse.RawDescriptionHelpFormatter,
58 parser.set_defaults(command=cmd)
61 def run(self, **kwargs):
62 """ Parse the command line arguments of the program and execute the
63 appropriate subcommand.
65 args = self.parser.parse_args(args=kwargs.get('cli_args'))
67 if args.subcommand is None:
68 self.parser.print_help()
71 for arg in ('module_dir', 'osm2pgsql_path', 'phplib_dir', 'sqllib_dir',
72 'data_dir', 'config_dir', 'phpcgi_path'):
73 setattr(args, arg, Path(kwargs[arg]))
74 args.project_dir = Path(args.project_dir).resolve()
76 logging.basicConfig(stream=sys.stderr,
77 format='%(asctime)s: %(message)s',
78 datefmt='%Y-%m-%d %H:%M:%S',
79 level=max(4 - args.verbose, 1) * 10)
81 args.config = Configuration(args.project_dir, args.config_dir)
83 log = logging.getLogger()
84 log.warning('Using project directory: %s', str(args.project_dir))
87 return args.command.run(args)
88 except UsageError as exception:
89 if log.isEnabledFor(logging.DEBUG):
90 raise # use Python's exception printing
91 log.fatal('FATAL: %s', exception)
93 # If we get here, then execution has failed in some way.
97 ##### Subcommand classes
99 # Each class needs to implement two functions: add_args() adds the CLI parameters
100 # for the subfunction, run() executes the subcommand.
102 # The class documentation doubles as the help text for the command. The
103 # first line is also used in the summary when calling the program without
106 # No need to document the functions each time.
107 # pylint: disable=C0111
108 # Using non-top-level imports to make pyosmium optional for replication only.
109 # pylint: disable=E0012,C0415
114 Create a new Nominatim database from an OSM file.
118 def add_args(parser):
119 group_name = parser.add_argument_group('Required arguments')
120 group = group_name.add_mutually_exclusive_group(required=True)
121 group.add_argument('--osm-file',
122 help='OSM file to be imported.')
123 group.add_argument('--continue', dest='continue_at',
124 choices=['load-data', 'indexing', 'db-postprocess'],
125 help='Continue an import that was interrupted')
126 group = parser.add_argument_group('Optional arguments')
127 group.add_argument('--osm2pgsql-cache', metavar='SIZE', type=int,
128 help='Size of cache to be used by osm2pgsql (in MB)')
129 group.add_argument('--reverse-only', action='store_true',
130 help='Do not create tables and indexes for searching')
131 group.add_argument('--enable-debug-statements', action='store_true',
132 help='Include debug warning statements in SQL code')
133 group.add_argument('--no-partitions', action='store_true',
134 help="""Do not partition search indices
135 (speeds up import of single country extracts)""")
136 group.add_argument('--no-updates', action='store_true',
137 help="""Do not keep tables that are only needed for
138 updating the database later""")
139 group = parser.add_argument_group('Expert options')
140 group.add_argument('--ignore-errors', action='store_true',
141 help='Continue import even when errors in SQL are present')
142 group.add_argument('--index-noanalyse', action='store_true',
143 help='Do not perform analyse operations during index')
148 params = ['setup.php']
150 params.extend(('--all', '--osm-file', args.osm_file))
152 if args.continue_at == 'load-data':
153 params.append('--load-data')
154 if args.continue_at in ('load-data', 'indexing'):
155 params.append('--index')
156 params.extend(('--create-search-indices', '--create-country-names',
158 if args.osm2pgsql_cache:
159 params.extend(('--osm2pgsql-cache', args.osm2pgsql_cache))
160 if args.reverse_only:
161 params.append('--reverse-only')
162 if args.enable_debug_statements:
163 params.append('--enable-debug-statements')
164 if args.no_partitions:
165 params.append('--no-partitions')
167 params.append('--drop')
168 if args.ignore_errors:
169 params.append('--ignore-errors')
170 if args.index_noanalyse:
171 params.append('--index-noanalyse')
173 return run_legacy_script(*params, nominatim_env=args)
176 class SetupSpecialPhrases:
178 Maintain special phrases.
182 def add_args(parser):
183 group = parser.add_argument_group('Input arguments')
184 group.add_argument('--from-wiki', action='store_true',
185 help='Pull special phrases from the OSM wiki.')
186 group = parser.add_argument_group('Output arguments')
187 group.add_argument('-o', '--output', default='-',
188 help="""File to write the preprocessed phrases to.
189 If omitted, it will be written to stdout.""")
193 if args.output != '-':
194 raise NotImplementedError('Only output to stdout is currently implemented.')
195 return run_legacy_script('specialphrases.php', '--wiki-import', nominatim_env=args)
200 Add additional data from a file or an online source.
202 Data is only imported, not indexed. You need to call `nominatim-update index`
203 to complete the process.
207 def add_args(parser):
208 group_name = parser.add_argument_group('Source')
209 group = group_name.add_mutually_exclusive_group(required=True)
210 group.add_argument('--file', metavar='FILE',
211 help='Import data from an OSM file')
212 group.add_argument('--diff', metavar='FILE',
213 help='Import data from an OSM diff file')
214 group.add_argument('--node', metavar='ID', type=int,
215 help='Import a single node from the API')
216 group.add_argument('--way', metavar='ID', type=int,
217 help='Import a single way from the API')
218 group.add_argument('--relation', metavar='ID', type=int,
219 help='Import a single relation from the API')
220 group.add_argument('--tiger-data', metavar='DIR',
221 help='Add housenumbers from the US TIGER census database.')
222 group = parser.add_argument_group('Extra arguments')
223 group.add_argument('--use-main-api', action='store_true',
224 help='Use OSM API instead of Overpass to download objects')
229 os.environ['NOMINATIM_TIGER_DATA_PATH'] = args.tiger_data
230 return run_legacy_script('setup.php', '--import-tiger-data', nominatim_env=args)
232 params = ['update.php']
234 params.extend(('--import-file', args.file))
236 params.extend(('--import-diff', args.diff))
238 params.extend(('--import-node', args.node))
240 params.extend(('--import-way', args.way))
242 params.extend(('--import-relation', args.relation))
243 if args.use_main_api:
244 params.append('--use-main-api')
245 return run_legacy_script(*params, nominatim_env=args)
250 Export addresses as CSV file from the database.
254 def add_args(parser):
255 group = parser.add_argument_group('Output arguments')
256 group.add_argument('--output-type', default='street',
257 choices=('continent', 'country', 'state', 'county',
258 'city', 'suburb', 'street', 'path'),
259 help='Type of places to output (default: street)')
260 group.add_argument('--output-format',
261 default='street;suburb;city;county;state;country',
262 help="""Semicolon-separated list of address types
263 (see --output-type). Multiple ranks can be
264 merged into one column by simply using a
265 comma-separated list.""")
266 group.add_argument('--output-all-postcodes', action='store_true',
267 help="""List all postcodes for address instead of
268 just the most likely one""")
269 group.add_argument('--language',
270 help="""Preferred language for output
271 (use local name, if omitted)""")
272 group = parser.add_argument_group('Filter arguments')
273 group.add_argument('--restrict-to-country', metavar='COUNTRY_CODE',
274 help='Export only objects within country')
275 group.add_argument('--restrict-to-osm-node', metavar='ID', type=int,
276 help='Export only children of this OSM node')
277 group.add_argument('--restrict-to-osm-way', metavar='ID', type=int,
278 help='Export only children of this OSM way')
279 group.add_argument('--restrict-to-osm-relation', metavar='ID', type=int,
280 help='Export only children of this OSM relation')
285 params = ['export.php',
286 '--output-type', args.output_type,
287 '--output-format', args.output_format]
288 if args.output_all_postcodes:
289 params.append('--output-all-postcodes')
291 params.extend(('--language', args.language))
292 if args.restrict_to_country:
293 params.extend(('--restrict-to-country', args.restrict_to_country))
294 if args.restrict_to_osm_node:
295 params.extend(('--restrict-to-osm-node', args.restrict_to_osm_node))
296 if args.restrict_to_osm_way:
297 params.extend(('--restrict-to-osm-way', args.restrict_to_osm_way))
298 if args.restrict_to_osm_relation:
299 params.extend(('--restrict-to-osm-relation', args.restrict_to_osm_relation))
301 return run_legacy_script(*params, nominatim_env=args)
306 Start a simple web server for serving the API.
308 This command starts the built-in PHP webserver to serve the website
309 from the current project directory. This webserver is only suitable
310 for testing and develop. Do not use it in production setups!
312 By the default, the webserver can be accessed at: http://127.0.0.1:8088
316 def add_args(parser):
317 group = parser.add_argument_group('Server arguments')
318 group.add_argument('--server', default='127.0.0.1:8088',
319 help='The address the server will listen to.')
323 run_php_server(args.server, args.project_dir / 'website')
326 def nominatim(**kwargs):
328 Command-line tools for importing, updating, administrating and
329 querying the Nominatim database.
331 parser = CommandlineParser('nominatim', nominatim.__doc__)
333 parser.add_subcommand('import', SetupAll)
334 parser.add_subcommand('freeze', clicmd.SetupFreeze)
335 parser.add_subcommand('replication', clicmd.UpdateReplication)
337 parser.add_subcommand('special-phrases', SetupSpecialPhrases)
339 parser.add_subcommand('add-data', UpdateAddData)
340 parser.add_subcommand('index', clicmd.UpdateIndex)
341 parser.add_subcommand('refresh', clicmd.UpdateRefresh)
343 parser.add_subcommand('admin', clicmd.AdminFuncs)
345 parser.add_subcommand('export', QueryExport)
346 parser.add_subcommand('serve', AdminServe)
348 if kwargs.get('phpcgi_path'):
349 parser.add_subcommand('search', clicmd.APISearch)
350 parser.add_subcommand('reverse', clicmd.APIReverse)
351 parser.add_subcommand('lookup', clicmd.APILookup)
352 parser.add_subcommand('details', clicmd.APIDetails)
353 parser.add_subcommand('status', clicmd.APIStatus)
355 parser.parser.epilog = 'php-cgi not found. Query commands not available.'
357 parser.add_subcommand('transition', clicmd.AdminTransition)
359 return parser.run(**kwargs)