]> git.openstreetmap.org Git - nominatim.git/blob - nominatim/cli.py
port check-for-update function to python
[nominatim.git] / nominatim / cli.py
1 """
2 Command-line interface to the Nominatim functions for import, update,
3 database administration and querying.
4 """
5 import sys
6 import os
7 import argparse
8 import logging
9 from pathlib import Path
10
11 from .config import Configuration
12 from .tools.exec_utils import run_legacy_script, run_api_script
13 from .db.connection import connect
14
15 LOG = logging.getLogger()
16
17 def _num_system_cpus():
18     try:
19         cpus = len(os.sched_getaffinity(0))
20     except NotImplementedError:
21         cpus = None
22
23     return cpus or os.cpu_count()
24
25
26 class CommandlineParser:
27     """ Wraps some of the common functions for parsing the command line
28         and setting up subcommands.
29     """
30     def __init__(self, prog, description):
31         self.parser = argparse.ArgumentParser(
32             prog=prog,
33             description=description,
34             formatter_class=argparse.RawDescriptionHelpFormatter)
35
36         self.subs = self.parser.add_subparsers(title='available commands',
37                                                dest='subcommand')
38
39         # Arguments added to every sub-command
40         self.default_args = argparse.ArgumentParser(add_help=False)
41         group = self.default_args.add_argument_group('Default arguments')
42         group.add_argument('-h', '--help', action='help',
43                            help='Show this help message and exit')
44         group.add_argument('-q', '--quiet', action='store_const', const=0,
45                            dest='verbose', default=1,
46                            help='Print only error messages')
47         group.add_argument('-v', '--verbose', action='count', default=1,
48                            help='Increase verboseness of output')
49         group.add_argument('--project-dir', metavar='DIR', default='.',
50                            help='Base directory of the Nominatim installation (default:.)')
51         group.add_argument('-j', '--threads', metavar='NUM', type=int,
52                            help='Number of parallel threads to use')
53
54
55     def add_subcommand(self, name, cmd):
56         """ Add a subcommand to the parser. The subcommand must be a class
57             with a function add_args() that adds the parameters for the
58             subcommand and a run() function that executes the command.
59         """
60         parser = self.subs.add_parser(name, parents=[self.default_args],
61                                       help=cmd.__doc__.split('\n', 1)[0],
62                                       description=cmd.__doc__,
63                                       formatter_class=argparse.RawDescriptionHelpFormatter,
64                                       add_help=False)
65         parser.set_defaults(command=cmd)
66         cmd.add_args(parser)
67
68     def run(self, **kwargs):
69         """ Parse the command line arguments of the program and execute the
70             appropriate subcommand.
71         """
72         args = self.parser.parse_args(args=kwargs.get('cli_args'))
73
74         if args.subcommand is None:
75             self.parser.print_help()
76             return 1
77
78         for arg in ('module_dir', 'osm2pgsql_path', 'phplib_dir', 'data_dir', 'phpcgi_path'):
79             setattr(args, arg, Path(kwargs[arg]))
80         args.project_dir = Path(args.project_dir)
81
82         logging.basicConfig(stream=sys.stderr,
83                             format='%(asctime)s: %(message)s',
84                             datefmt='%Y-%m-%d %H:%M:%S',
85                             level=max(4 - args.verbose, 1) * 10)
86
87         args.config = Configuration(args.project_dir, args.data_dir / 'settings')
88
89         return args.command.run(args)
90
91 ##### Subcommand classes
92 #
93 # Each class needs to implement two functions: add_args() adds the CLI parameters
94 # for the subfunction, run() executes the subcommand.
95 #
96 # The class documentation doubles as the help text for the command. The
97 # first line is also used in the summary when calling the program without
98 # a subcommand.
99 #
100 # No need to document the functions each time.
101 # pylint: disable=C0111
102
103
104 class SetupAll:
105     """\
106     Create a new Nominatim database from an OSM file.
107     """
108
109     @staticmethod
110     def add_args(parser):
111         group_name = parser.add_argument_group('Required arguments')
112         group = group_name.add_mutually_exclusive_group(required=True)
113         group.add_argument('--osm-file',
114                            help='OSM file to be imported.')
115         group.add_argument('--continue', dest='continue_at',
116                            choices=['load-data', 'indexing', 'db-postprocess'],
117                            help='Continue an import that was interrupted')
118         group = parser.add_argument_group('Optional arguments')
119         group.add_argument('--osm2pgsql-cache', metavar='SIZE', type=int,
120                            help='Size of cache to be used by osm2pgsql (in MB)')
121         group.add_argument('--reverse-only', action='store_true',
122                            help='Do not create tables and indexes for searching')
123         group.add_argument('--enable-debug-statements', action='store_true',
124                            help='Include debug warning statements in SQL code')
125         group.add_argument('--no-partitions', action='store_true',
126                            help="""Do not partition search indices
127                                    (speeds up import of single country extracts)""")
128         group.add_argument('--no-updates', action='store_true',
129                            help="""Do not keep tables that are only needed for
130                                    updating the database later""")
131         group = parser.add_argument_group('Expert options')
132         group.add_argument('--ignore-errors', action='store_true',
133                            help='Continue import even when errors in SQL are present')
134         group.add_argument('--index-noanalyse', action='store_true',
135                            help='Do not perform analyse operations during index')
136
137
138     @staticmethod
139     def run(args):
140         params = ['setup.php']
141         if args.osm_file:
142             params.extend(('--all', '--osm-file', args.osm_file))
143         else:
144             if args.continue_at == 'load-data':
145                 params.append('--load-data')
146             if args.continue_at in ('load-data', 'indexing'):
147                 params.append('--index')
148             params.extend(('--create-search-indices', '--create-country-names',
149                            '--setup-website'))
150         if args.osm2pgsql_cache:
151             params.extend(('--osm2pgsql-cache', args.osm2pgsql_cache))
152         if args.reverse_only:
153             params.append('--reverse-only')
154         if args.enable_debug_statements:
155             params.append('--enable-debug-statements')
156         if args.no_partitions:
157             params.append('--no-partitions')
158         if args.no_updates:
159             params.append('--drop')
160         if args.ignore_errors:
161             params.append('--ignore-errors')
162         if args.index_noanalyse:
163             params.append('--index-noanalyse')
164
165         return run_legacy_script(*params, nominatim_env=args)
166
167
168 class SetupFreeze:
169     """\
170     Make database read-only.
171
172     About half of data in the Nominatim database is kept only to be able to
173     keep the data up-to-date with new changes made in OpenStreetMap. This
174     command drops all this data and only keeps the part needed for geocoding
175     itself.
176
177     This command has the same effect as the `--no-updates` option for imports.
178     """
179
180     @staticmethod
181     def add_args(parser):
182         pass # No options
183
184     @staticmethod
185     def run(args):
186         return run_legacy_script('setup.php', '--drop', nominatim_env=args)
187
188
189 class SetupSpecialPhrases:
190     """\
191     Maintain special phrases.
192     """
193
194     @staticmethod
195     def add_args(parser):
196         group = parser.add_argument_group('Input arguments')
197         group.add_argument('--from-wiki', action='store_true',
198                            help='Pull special phrases from the OSM wiki.')
199         group = parser.add_argument_group('Output arguments')
200         group.add_argument('-o', '--output', default='-',
201                            help="""File to write the preprocessed phrases to.
202                                    If omitted, it will be written to stdout.""")
203
204     @staticmethod
205     def run(args):
206         if args.output != '-':
207             raise NotImplementedError('Only output to stdout is currently implemented.')
208         return run_legacy_script('specialphrases.php', '--wiki-import', nominatim_env=args)
209
210
211 class UpdateReplication:
212     """\
213     Update the database using an online replication service.
214     """
215
216     @staticmethod
217     def add_args(parser):
218         group = parser.add_argument_group('Arguments for initialisation')
219         group.add_argument('--init', action='store_true',
220                            help='Initialise the update process')
221         group.add_argument('--no-update-functions', dest='update_functions',
222                            action='store_false',
223                            help="""Do not update the trigger function to
224                                    support differential updates.""")
225         group = parser.add_argument_group('Arguments for updates')
226         group.add_argument('--check-for-updates', action='store_true',
227                            help='Check if new updates are available and exit')
228         group.add_argument('--once', action='store_true',
229                            help="""Download and apply updates only once. When
230                                    not set, updates are continuously applied""")
231         group.add_argument('--no-index', action='store_false', dest='do_index',
232                            help="""Do not index the new data. Only applicable
233                                    together with --once""")
234
235     @staticmethod
236     def run(args):
237         try:
238             import osmium # pylint: disable=W0611
239         except ModuleNotFoundError:
240             LOG.fatal("pyosmium not installed. Replication functions not available.\n"
241                       "To install pyosmium via pip: pip3 install osmium")
242             return 1
243
244         from .tools import replication, refresh
245
246         conn = connect(args.config.get_libpq_dsn())
247
248         params = ['update.php']
249         if args.init:
250             LOG.warning("Initialising replication updates")
251             replication.init_replication(conn, args.config.REPLICATION_URL)
252             if args.update_functions:
253                 LOG.warning("Create functions")
254                 refresh.create_functions(conn, args.config, args.data_dir,
255                                          True, False)
256             conn.close()
257             return 0
258
259         if args.check_for_updates:
260             ret = replication.check_for_updates(conn, args.config.REPLICATION_URL)
261             conn.close()
262             return ret
263
264         if args.once:
265             params.append('--import-osmosis')
266         else:
267             params.append('--import-osmosis-all')
268         if not args.do_index:
269             params.append('--no-index')
270
271         return run_legacy_script(*params, nominatim_env=args)
272
273
274 class UpdateAddData:
275     """\
276     Add additional data from a file or an online source.
277
278     Data is only imported, not indexed. You need to call `nominatim-update index`
279     to complete the process.
280     """
281
282     @staticmethod
283     def add_args(parser):
284         group_name = parser.add_argument_group('Source')
285         group = group_name.add_mutually_exclusive_group(required=True)
286         group.add_argument('--file', metavar='FILE',
287                            help='Import data from an OSM file')
288         group.add_argument('--diff', metavar='FILE',
289                            help='Import data from an OSM diff file')
290         group.add_argument('--node', metavar='ID', type=int,
291                            help='Import a single node from the API')
292         group.add_argument('--way', metavar='ID', type=int,
293                            help='Import a single way from the API')
294         group.add_argument('--relation', metavar='ID', type=int,
295                            help='Import a single relation from the API')
296         group.add_argument('--tiger-data', metavar='DIR',
297                            help='Add housenumbers from the US TIGER census database.')
298         group = parser.add_argument_group('Extra arguments')
299         group.add_argument('--use-main-api', action='store_true',
300                            help='Use OSM API instead of Overpass to download objects')
301
302     @staticmethod
303     def run(args):
304         if args.tiger_data:
305             os.environ['NOMINATIM_TIGER_DATA_PATH'] = args.tiger_data
306             return run_legacy_script('setup.php', '--import-tiger-data', nominatim_env=args)
307
308         params = ['update.php']
309         if args.file:
310             params.extend(('--import-file', args.file))
311         elif args.diff:
312             params.extend(('--import-diff', args.diff))
313         elif args.node:
314             params.extend(('--import-node', args.node))
315         elif args.way:
316             params.extend(('--import-way', args.way))
317         elif args.relation:
318             params.extend(('--import-relation', args.relation))
319         if args.use_main_api:
320             params.append('--use-main-api')
321         return run_legacy_script(*params, nominatim_env=args)
322
323
324 class UpdateIndex:
325     """\
326     Reindex all new and modified data.
327     """
328
329     @staticmethod
330     def add_args(parser):
331         group = parser.add_argument_group('Filter arguments')
332         group.add_argument('--boundaries-only', action='store_true',
333                            help="""Index only administrative boundaries.""")
334         group.add_argument('--no-boundaries', action='store_true',
335                            help="""Index everything except administrative boundaries.""")
336         group.add_argument('--minrank', '-r', type=int, metavar='RANK', default=0,
337                            help='Minimum/starting rank')
338         group.add_argument('--maxrank', '-R', type=int, metavar='RANK', default=30,
339                            help='Maximum/finishing rank')
340
341     @staticmethod
342     def run(args):
343         from .indexer.indexer import Indexer
344
345         indexer = Indexer(args.config.get_libpq_dsn(),
346                           args.threads or _num_system_cpus() or 1)
347
348         if not args.no_boundaries:
349             indexer.index_boundaries(args.minrank, args.maxrank)
350         if not args.boundaries_only:
351             indexer.index_by_rank(args.minrank, args.maxrank)
352
353         if not args.no_boundaries and not args.boundaries_only:
354             indexer.update_status_table()
355
356         return 0
357
358
359 class UpdateRefresh:
360     """\
361     Recompute auxiliary data used by the indexing process.
362
363     These functions must not be run in parallel with other update commands.
364     """
365
366     @staticmethod
367     def add_args(parser):
368         group = parser.add_argument_group('Data arguments')
369         group.add_argument('--postcodes', action='store_true',
370                            help='Update postcode centroid table')
371         group.add_argument('--word-counts', action='store_true',
372                            help='Compute frequency of full-word search terms')
373         group.add_argument('--address-levels', action='store_true',
374                            help='Reimport address level configuration')
375         group.add_argument('--functions', action='store_true',
376                            help='Update the PL/pgSQL functions in the database')
377         group.add_argument('--wiki-data', action='store_true',
378                            help='Update Wikipedia/data importance numbers.')
379         group.add_argument('--importance', action='store_true',
380                            help='Recompute place importances (expensive!)')
381         group.add_argument('--website', action='store_true',
382                            help='Refresh the directory that serves the scripts for the web API')
383         group = parser.add_argument_group('Arguments for function refresh')
384         group.add_argument('--no-diff-updates', action='store_false', dest='diffs',
385                            help='Do not enable code for propagating updates')
386         group.add_argument('--enable-debug-statements', action='store_true',
387                            help='Enable debug warning statements in functions')
388
389     @staticmethod
390     def run(args):
391         from .tools import refresh
392
393         conn = connect(args.config.get_libpq_dsn())
394
395         if args.postcodes:
396             LOG.warning("Update postcodes centroid")
397             refresh.update_postcodes(conn, args.data_dir)
398
399         if args.word_counts:
400             LOG.warning('Recompute frequency of full-word search terms')
401             refresh.recompute_word_counts(conn, args.data_dir)
402
403         if args.address_levels:
404             cfg = Path(args.config.ADDRESS_LEVEL_CONFIG)
405             LOG.warning('Updating address levels from %s', cfg)
406             refresh.load_address_levels_from_file(conn, cfg)
407
408         if args.functions:
409             LOG.warning('Create functions')
410             refresh.create_functions(conn, args.config, args.data_dir,
411                                      args.diffs, args.enable_debug_statements)
412
413         if args.wiki_data:
414             run_legacy_script('setup.php', '--import-wikipedia-articles',
415                               nominatim_env=args, throw_on_fail=True)
416         # Attention: importance MUST come after wiki data import.
417         if args.importance:
418             run_legacy_script('update.php', '--recompute-importance',
419                               nominatim_env=args, throw_on_fail=True)
420         if args.website:
421             run_legacy_script('setup.php', '--setup-website',
422                               nominatim_env=args, throw_on_fail=True)
423
424         conn.close()
425
426         return 0
427
428
429 class AdminCheckDatabase:
430     """\
431     Check that the database is complete and operational.
432     """
433
434     @staticmethod
435     def add_args(parser):
436         pass # No options
437
438     @staticmethod
439     def run(args):
440         return run_legacy_script('check_import_finished.php', nominatim_env=args)
441
442
443 class AdminWarm:
444     """\
445     Warm database caches for search and reverse queries.
446     """
447
448     @staticmethod
449     def add_args(parser):
450         group = parser.add_argument_group('Target arguments')
451         group.add_argument('--search-only', action='store_const', dest='target',
452                            const='search',
453                            help="Only pre-warm tables for search queries")
454         group.add_argument('--reverse-only', action='store_const', dest='target',
455                            const='reverse',
456                            help="Only pre-warm tables for reverse queries")
457
458     @staticmethod
459     def run(args):
460         params = ['warm.php']
461         if args.target == 'reverse':
462             params.append('--reverse-only')
463         if args.target == 'search':
464             params.append('--search-only')
465         return run_legacy_script(*params, nominatim_env=args)
466
467
468 class QueryExport:
469     """\
470     Export addresses as CSV file from the database.
471     """
472
473     @staticmethod
474     def add_args(parser):
475         group = parser.add_argument_group('Output arguments')
476         group.add_argument('--output-type', default='street',
477                            choices=('continent', 'country', 'state', 'county',
478                                     'city', 'suburb', 'street', 'path'),
479                            help='Type of places to output (default: street)')
480         group.add_argument('--output-format',
481                            default='street;suburb;city;county;state;country',
482                            help="""Semicolon-separated list of address types
483                                    (see --output-type). Multiple ranks can be
484                                    merged into one column by simply using a
485                                    comma-separated list.""")
486         group.add_argument('--output-all-postcodes', action='store_true',
487                            help="""List all postcodes for address instead of
488                                    just the most likely one""")
489         group.add_argument('--language',
490                            help="""Preferred language for output
491                                    (use local name, if omitted)""")
492         group = parser.add_argument_group('Filter arguments')
493         group.add_argument('--restrict-to-country', metavar='COUNTRY_CODE',
494                            help='Export only objects within country')
495         group.add_argument('--restrict-to-osm-node', metavar='ID', type=int,
496                            help='Export only children of this OSM node')
497         group.add_argument('--restrict-to-osm-way', metavar='ID', type=int,
498                            help='Export only children of this OSM way')
499         group.add_argument('--restrict-to-osm-relation', metavar='ID', type=int,
500                            help='Export only children of this OSM relation')
501
502
503     @staticmethod
504     def run(args):
505         params = ['export.php',
506                   '--output-type', args.output_type,
507                   '--output-format', args.output_format]
508         if args.output_all_postcodes:
509             params.append('--output-all-postcodes')
510         if args.language:
511             params.extend(('--language', args.language))
512         if args.restrict_to_country:
513             params.extend(('--restrict-to-country', args.restrict_to_country))
514         if args.restrict_to_osm_node:
515             params.extend(('--restrict-to-osm-node', args.restrict_to_osm_node))
516         if args.restrict_to_osm_way:
517             params.extend(('--restrict-to-osm-way', args.restrict_to_osm_way))
518         if args.restrict_to_osm_relation:
519             params.extend(('--restrict-to-osm-relation', args.restrict_to_osm_relation))
520
521         return run_legacy_script(*params, nominatim_env=args)
522
523 STRUCTURED_QUERY = (
524     ('street', 'housenumber and street'),
525     ('city', 'city, town or village'),
526     ('county', 'county'),
527     ('state', 'state'),
528     ('country', 'country'),
529     ('postalcode', 'postcode')
530 )
531
532 EXTRADATA_PARAMS = (
533     ('addressdetails', 'Include a breakdown of the address into elements.'),
534     ('extratags', """Include additional information if available
535                      (e.g. wikipedia link, opening hours)."""),
536     ('namedetails', 'Include a list of alternative names.')
537 )
538
539 DETAILS_SWITCHES = (
540     ('addressdetails', 'Include a breakdown of the address into elements.'),
541     ('keywords', 'Include a list of name keywords and address keywords.'),
542     ('linkedplaces', 'Include a details of places that are linked with this one.'),
543     ('hierarchy', 'Include details of places lower in the address hierarchy.'),
544     ('group_hierarchy', 'Group the places by type.'),
545     ('polygon_geojson', 'Include geometry of result.')
546 )
547
548 def _add_api_output_arguments(parser):
549     group = parser.add_argument_group('Output arguments')
550     group.add_argument('--format', default='jsonv2',
551                        choices=['xml', 'json', 'jsonv2', 'geojson', 'geocodejson'],
552                        help='Format of result')
553     for name, desc in EXTRADATA_PARAMS:
554         group.add_argument('--' + name, action='store_true', help=desc)
555
556     group.add_argument('--lang', '--accept-language', metavar='LANGS',
557                        help='Preferred language order for presenting search results')
558     group.add_argument('--polygon-output',
559                        choices=['geojson', 'kml', 'svg', 'text'],
560                        help='Output geometry of results as a GeoJSON, KML, SVG or WKT.')
561     group.add_argument('--polygon-threshold', type=float, metavar='TOLERANCE',
562                        help="""Simplify output geometry.
563                                Parameter is difference tolerance in degrees.""")
564
565
566 class APISearch:
567     """\
568     Execute API search query.
569     """
570
571     @staticmethod
572     def add_args(parser):
573         group = parser.add_argument_group('Query arguments')
574         group.add_argument('--query',
575                            help='Free-form query string')
576         for name, desc in STRUCTURED_QUERY:
577             group.add_argument('--' + name, help='Structured query: ' + desc)
578
579         _add_api_output_arguments(parser)
580
581         group = parser.add_argument_group('Result limitation')
582         group.add_argument('--countrycodes', metavar='CC,..',
583                            help='Limit search results to one or more countries.')
584         group.add_argument('--exclude_place_ids', metavar='ID,..',
585                            help='List of search object to be excluded')
586         group.add_argument('--limit', type=int,
587                            help='Limit the number of returned results')
588         group.add_argument('--viewbox', metavar='X1,Y1,X2,Y2',
589                            help='Preferred area to find search results')
590         group.add_argument('--bounded', action='store_true',
591                            help='Strictly restrict results to viewbox area')
592
593         group = parser.add_argument_group('Other arguments')
594         group.add_argument('--no-dedupe', action='store_false', dest='dedupe',
595                            help='Do not remove duplicates from the result list')
596
597
598     @staticmethod
599     def run(args):
600         if args.query:
601             params = dict(q=args.query)
602         else:
603             params = {k : getattr(args, k) for k, _ in STRUCTURED_QUERY if getattr(args, k)}
604
605         for param, _ in EXTRADATA_PARAMS:
606             if getattr(args, param):
607                 params[param] = '1'
608         for param in ('format', 'countrycodes', 'exclude_place_ids', 'limit', 'viewbox'):
609             if getattr(args, param):
610                 params[param] = getattr(args, param)
611         if args.lang:
612             params['accept-language'] = args.lang
613         if args.polygon_output:
614             params['polygon_' + args.polygon_output] = '1'
615         if args.polygon_threshold:
616             params['polygon_threshold'] = args.polygon_threshold
617         if args.bounded:
618             params['bounded'] = '1'
619         if not args.dedupe:
620             params['dedupe'] = '0'
621
622         return run_api_script('search', args.project_dir,
623                               phpcgi_bin=args.phpcgi_path, params=params)
624
625 class APIReverse:
626     """\
627     Execute API reverse query.
628     """
629
630     @staticmethod
631     def add_args(parser):
632         group = parser.add_argument_group('Query arguments')
633         group.add_argument('--lat', type=float, required=True,
634                            help='Latitude of coordinate to look up (in WGS84)')
635         group.add_argument('--lon', type=float, required=True,
636                            help='Longitude of coordinate to look up (in WGS84)')
637         group.add_argument('--zoom', type=int,
638                            help='Level of detail required for the address')
639
640         _add_api_output_arguments(parser)
641
642
643     @staticmethod
644     def run(args):
645         params = dict(lat=args.lat, lon=args.lon)
646         if args.zoom is not None:
647             params['zoom'] = args.zoom
648
649         for param, _ in EXTRADATA_PARAMS:
650             if getattr(args, param):
651                 params[param] = '1'
652         if args.format:
653             params['format'] = args.format
654         if args.lang:
655             params['accept-language'] = args.lang
656         if args.polygon_output:
657             params['polygon_' + args.polygon_output] = '1'
658         if args.polygon_threshold:
659             params['polygon_threshold'] = args.polygon_threshold
660
661         return run_api_script('reverse', args.project_dir,
662                               phpcgi_bin=args.phpcgi_path, params=params)
663
664
665 class APILookup:
666     """\
667     Execute API reverse query.
668     """
669
670     @staticmethod
671     def add_args(parser):
672         group = parser.add_argument_group('Query arguments')
673         group.add_argument('--id', metavar='OSMID',
674                            action='append', required=True, dest='ids',
675                            help='OSM id to lookup in format <NRW><id> (may be repeated)')
676
677         _add_api_output_arguments(parser)
678
679
680     @staticmethod
681     def run(args):
682         params = dict(osm_ids=','.join(args.ids))
683
684         for param, _ in EXTRADATA_PARAMS:
685             if getattr(args, param):
686                 params[param] = '1'
687         if args.format:
688             params['format'] = args.format
689         if args.lang:
690             params['accept-language'] = args.lang
691         if args.polygon_output:
692             params['polygon_' + args.polygon_output] = '1'
693         if args.polygon_threshold:
694             params['polygon_threshold'] = args.polygon_threshold
695
696         return run_api_script('lookup', args.project_dir,
697                               phpcgi_bin=args.phpcgi_path, params=params)
698
699
700 class APIDetails:
701     """\
702     Execute API lookup query.
703     """
704
705     @staticmethod
706     def add_args(parser):
707         group = parser.add_argument_group('Query arguments')
708         objs = group.add_mutually_exclusive_group(required=True)
709         objs.add_argument('--node', '-n', type=int,
710                           help="Look up the OSM node with the given ID.")
711         objs.add_argument('--way', '-w', type=int,
712                           help="Look up the OSM way with the given ID.")
713         objs.add_argument('--relation', '-r', type=int,
714                           help="Look up the OSM relation with the given ID.")
715         objs.add_argument('--place_id', '-p', type=int,
716                           help='Database internal identifier of the OSM object to look up.')
717         group.add_argument('--class', dest='object_class',
718                            help="""Class type to disambiguated multiple entries
719                                    of the same object.""")
720
721         group = parser.add_argument_group('Output arguments')
722         for name, desc in DETAILS_SWITCHES:
723             group.add_argument('--' + name, action='store_true', help=desc)
724         group.add_argument('--lang', '--accept-language', metavar='LANGS',
725                            help='Preferred language order for presenting search results')
726
727     @staticmethod
728     def run(args):
729         if args.node:
730             params = dict(osmtype='N', osmid=args.node)
731         elif args.way:
732             params = dict(osmtype='W', osmid=args.node)
733         elif args.relation:
734             params = dict(osmtype='R', osmid=args.node)
735         else:
736             params = dict(place_id=args.place_id)
737         if args.object_class:
738             params['class'] = args.object_class
739         for name, _ in DETAILS_SWITCHES:
740             params[name] = '1' if getattr(args, name) else '0'
741
742         return run_api_script('details', args.project_dir,
743                               phpcgi_bin=args.phpcgi_path, params=params)
744
745
746 class APIStatus:
747     """\
748     Execute API status query.
749     """
750
751     @staticmethod
752     def add_args(parser):
753         group = parser.add_argument_group('API parameters')
754         group.add_argument('--format', default='text', choices=['text', 'json'],
755                            help='Format of result')
756
757     @staticmethod
758     def run(args):
759         return run_api_script('status', args.project_dir,
760                               phpcgi_bin=args.phpcgi_path,
761                               params=dict(format=args.format))
762
763
764 def nominatim(**kwargs):
765     """\
766     Command-line tools for importing, updating, administrating and
767     querying the Nominatim database.
768     """
769     parser = CommandlineParser('nominatim', nominatim.__doc__)
770
771     parser.add_subcommand('import', SetupAll)
772     parser.add_subcommand('freeze', SetupFreeze)
773     parser.add_subcommand('replication', UpdateReplication)
774
775     parser.add_subcommand('check-database', AdminCheckDatabase)
776     parser.add_subcommand('warm', AdminWarm)
777
778     parser.add_subcommand('special-phrases', SetupSpecialPhrases)
779
780     parser.add_subcommand('add-data', UpdateAddData)
781     parser.add_subcommand('index', UpdateIndex)
782     parser.add_subcommand('refresh', UpdateRefresh)
783
784     parser.add_subcommand('export', QueryExport)
785
786     if kwargs.get('phpcgi_path'):
787         parser.add_subcommand('search', APISearch)
788         parser.add_subcommand('reverse', APIReverse)
789         parser.add_subcommand('lookup', APILookup)
790         parser.add_subcommand('details', APIDetails)
791         parser.add_subcommand('status', APIStatus)
792     else:
793         parser.parser.epilog = 'php-cgi not found. Query commands not available.'
794
795     return parser.run(**kwargs)