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
15 from .clicmd.args import NominatimArgs
17 LOG = logging.getLogger()
20 class CommandlineParser:
21 """ Wraps some of the common functions for parsing the command line
22 and setting up subcommands.
24 def __init__(self, prog, description):
25 self.parser = argparse.ArgumentParser(
27 description=description,
28 formatter_class=argparse.RawDescriptionHelpFormatter)
30 self.subs = self.parser.add_subparsers(title='available commands',
33 # Arguments added to every sub-command
34 self.default_args = argparse.ArgumentParser(add_help=False)
35 group = self.default_args.add_argument_group('Default arguments')
36 group.add_argument('-h', '--help', action='help',
37 help='Show this help message and exit')
38 group.add_argument('-q', '--quiet', action='store_const', const=0,
39 dest='verbose', default=1,
40 help='Print only error messages')
41 group.add_argument('-v', '--verbose', action='count', default=1,
42 help='Increase verboseness of output')
43 group.add_argument('--project-dir', metavar='DIR', default='.',
44 help='Base directory of the Nominatim installation (default:.)')
45 group.add_argument('-j', '--threads', metavar='NUM', type=int,
46 help='Number of parallel threads to use')
49 def add_subcommand(self, name, cmd):
50 """ Add a subcommand to the parser. The subcommand must be a class
51 with a function add_args() that adds the parameters for the
52 subcommand and a run() function that executes the command.
54 parser = self.subs.add_parser(name, parents=[self.default_args],
55 help=cmd.__doc__.split('\n', 1)[0],
56 description=cmd.__doc__,
57 formatter_class=argparse.RawDescriptionHelpFormatter,
59 parser.set_defaults(command=cmd)
62 def run(self, **kwargs):
63 """ Parse the command line arguments of the program and execute the
64 appropriate subcommand.
66 args = NominatimArgs()
67 self.parser.parse_args(args=kwargs.get('cli_args'), namespace=args)
69 if args.subcommand is None:
70 self.parser.print_help()
73 for arg in ('module_dir', 'osm2pgsql_path', 'phplib_dir', 'sqllib_dir',
74 'data_dir', 'config_dir', 'phpcgi_path'):
75 setattr(args, arg, Path(kwargs[arg]))
76 args.project_dir = Path(args.project_dir).resolve()
78 if 'cli_args' not in kwargs:
79 logging.basicConfig(stream=sys.stderr,
80 format='%(asctime)s: %(message)s',
81 datefmt='%Y-%m-%d %H:%M:%S',
82 level=max(4 - args.verbose, 1) * 10)
84 args.config = Configuration(args.project_dir, args.config_dir,
85 environ=kwargs.get('environ', os.environ))
87 log = logging.getLogger()
88 log.warning('Using project directory: %s', str(args.project_dir))
91 return args.command.run(args)
92 except UsageError as exception:
93 if log.isEnabledFor(logging.DEBUG):
94 raise # use Python's exception printing
95 log.fatal('FATAL: %s', exception)
97 # If we get here, then execution has failed in some way.
101 ##### Subcommand classes
103 # Each class needs to implement two functions: add_args() adds the CLI parameters
104 # for the subfunction, run() executes the subcommand.
106 # The class documentation doubles as the help text for the command. The
107 # first line is also used in the summary when calling the program without
110 # No need to document the functions each time.
111 # pylint: disable=C0111
112 # Using non-top-level imports to make pyosmium optional for replication only.
113 # pylint: disable=E0012,C0415
116 class SetupSpecialPhrases:
118 Maintain special phrases.
122 def add_args(parser):
123 group = parser.add_argument_group('Input arguments')
124 group.add_argument('--from-wiki', action='store_true',
125 help='Pull special phrases from the OSM wiki.')
126 group = parser.add_argument_group('Output arguments')
127 group.add_argument('-o', '--output', default='-',
128 help="""File to write the preprocessed phrases to.
129 If omitted, it will be written to stdout.""")
133 if args.output != '-':
134 raise NotImplementedError('Only output to stdout is currently implemented.')
135 return run_legacy_script('specialphrases.php', '--wiki-import', nominatim_env=args)
140 Add additional data from a file or an online source.
142 Data is only imported, not indexed. You need to call `nominatim-update index`
143 to complete the process.
147 def add_args(parser):
148 group_name = parser.add_argument_group('Source')
149 group = group_name.add_mutually_exclusive_group(required=True)
150 group.add_argument('--file', metavar='FILE',
151 help='Import data from an OSM file')
152 group.add_argument('--diff', metavar='FILE',
153 help='Import data from an OSM diff file')
154 group.add_argument('--node', metavar='ID', type=int,
155 help='Import a single node from the API')
156 group.add_argument('--way', metavar='ID', type=int,
157 help='Import a single way from the API')
158 group.add_argument('--relation', metavar='ID', type=int,
159 help='Import a single relation from the API')
160 group.add_argument('--tiger-data', metavar='DIR',
161 help='Add housenumbers from the US TIGER census database.')
162 group = parser.add_argument_group('Extra arguments')
163 group.add_argument('--use-main-api', action='store_true',
164 help='Use OSM API instead of Overpass to download objects')
169 os.environ['NOMINATIM_TIGER_DATA_PATH'] = args.tiger_data
170 return run_legacy_script('setup.php', '--import-tiger-data', nominatim_env=args)
172 params = ['update.php']
174 params.extend(('--import-file', args.file))
176 params.extend(('--import-diff', args.diff))
178 params.extend(('--import-node', args.node))
180 params.extend(('--import-way', args.way))
182 params.extend(('--import-relation', args.relation))
183 if args.use_main_api:
184 params.append('--use-main-api')
185 return run_legacy_script(*params, nominatim_env=args)
190 Export addresses as CSV file from the database.
194 def add_args(parser):
195 group = parser.add_argument_group('Output arguments')
196 group.add_argument('--output-type', default='street',
197 choices=('continent', 'country', 'state', 'county',
198 'city', 'suburb', 'street', 'path'),
199 help='Type of places to output (default: street)')
200 group.add_argument('--output-format',
201 default='street;suburb;city;county;state;country',
202 help="""Semicolon-separated list of address types
203 (see --output-type). Multiple ranks can be
204 merged into one column by simply using a
205 comma-separated list.""")
206 group.add_argument('--output-all-postcodes', action='store_true',
207 help="""List all postcodes for address instead of
208 just the most likely one""")
209 group.add_argument('--language',
210 help="""Preferred language for output
211 (use local name, if omitted)""")
212 group = parser.add_argument_group('Filter arguments')
213 group.add_argument('--restrict-to-country', metavar='COUNTRY_CODE',
214 help='Export only objects within country')
215 group.add_argument('--restrict-to-osm-node', metavar='ID', type=int,
216 help='Export only children of this OSM node')
217 group.add_argument('--restrict-to-osm-way', metavar='ID', type=int,
218 help='Export only children of this OSM way')
219 group.add_argument('--restrict-to-osm-relation', metavar='ID', type=int,
220 help='Export only children of this OSM relation')
225 params = ['export.php',
226 '--output-type', args.output_type,
227 '--output-format', args.output_format]
228 if args.output_all_postcodes:
229 params.append('--output-all-postcodes')
231 params.extend(('--language', args.language))
232 if args.restrict_to_country:
233 params.extend(('--restrict-to-country', args.restrict_to_country))
234 if args.restrict_to_osm_node:
235 params.extend(('--restrict-to-osm-node', args.restrict_to_osm_node))
236 if args.restrict_to_osm_way:
237 params.extend(('--restrict-to-osm-way', args.restrict_to_osm_way))
238 if args.restrict_to_osm_relation:
239 params.extend(('--restrict-to-osm-relation', args.restrict_to_osm_relation))
241 return run_legacy_script(*params, nominatim_env=args)
246 Start a simple web server for serving the API.
248 This command starts the built-in PHP webserver to serve the website
249 from the current project directory. This webserver is only suitable
250 for testing and develop. Do not use it in production setups!
252 By the default, the webserver can be accessed at: http://127.0.0.1:8088
256 def add_args(parser):
257 group = parser.add_argument_group('Server arguments')
258 group.add_argument('--server', default='127.0.0.1:8088',
259 help='The address the server will listen to.')
263 run_php_server(args.server, args.project_dir / 'website')
266 def nominatim(**kwargs):
268 Command-line tools for importing, updating, administrating and
269 querying the Nominatim database.
271 parser = CommandlineParser('nominatim', nominatim.__doc__)
273 parser.add_subcommand('import', clicmd.SetupAll)
274 parser.add_subcommand('freeze', clicmd.SetupFreeze)
275 parser.add_subcommand('replication', clicmd.UpdateReplication)
277 parser.add_subcommand('special-phrases', SetupSpecialPhrases)
279 parser.add_subcommand('add-data', UpdateAddData)
280 parser.add_subcommand('index', clicmd.UpdateIndex)
281 parser.add_subcommand('refresh', clicmd.UpdateRefresh)
283 parser.add_subcommand('admin', clicmd.AdminFuncs)
285 parser.add_subcommand('export', QueryExport)
286 parser.add_subcommand('serve', AdminServe)
288 if kwargs.get('phpcgi_path'):
289 parser.add_subcommand('search', clicmd.APISearch)
290 parser.add_subcommand('reverse', clicmd.APIReverse)
291 parser.add_subcommand('lookup', clicmd.APILookup)
292 parser.add_subcommand('details', clicmd.APIDetails)
293 parser.add_subcommand('status', clicmd.APIStatus)
295 parser.parser.epilog = 'php-cgi not found. Query commands not available.'
297 parser.add_subcommand('transition', clicmd.AdminTransition)
299 return parser.run(**kwargs)