2 Command-line interface to the Nominatim functions for import, update,
3 database administration and querying.
11 from pathlib import Path
13 from .config import Configuration
14 from .tools.exec_utils import run_legacy_script, run_api_script
15 from .db.connection import connect
16 from .db import status
17 from .errors import UsageError
19 LOG = logging.getLogger()
21 def _num_system_cpus():
23 cpus = len(os.sched_getaffinity(0))
24 except NotImplementedError:
27 return cpus or os.cpu_count()
30 class CommandlineParser:
31 """ Wraps some of the common functions for parsing the command line
32 and setting up subcommands.
34 def __init__(self, prog, description):
35 self.parser = argparse.ArgumentParser(
37 description=description,
38 formatter_class=argparse.RawDescriptionHelpFormatter)
40 self.subs = self.parser.add_subparsers(title='available commands',
43 # Arguments added to every sub-command
44 self.default_args = argparse.ArgumentParser(add_help=False)
45 group = self.default_args.add_argument_group('Default arguments')
46 group.add_argument('-h', '--help', action='help',
47 help='Show this help message and exit')
48 group.add_argument('-q', '--quiet', action='store_const', const=0,
49 dest='verbose', default=1,
50 help='Print only error messages')
51 group.add_argument('-v', '--verbose', action='count', default=1,
52 help='Increase verboseness of output')
53 group.add_argument('--project-dir', metavar='DIR', default='.',
54 help='Base directory of the Nominatim installation (default:.)')
55 group.add_argument('-j', '--threads', metavar='NUM', type=int,
56 help='Number of parallel threads to use')
59 def add_subcommand(self, name, cmd):
60 """ Add a subcommand to the parser. The subcommand must be a class
61 with a function add_args() that adds the parameters for the
62 subcommand and a run() function that executes the command.
64 parser = self.subs.add_parser(name, parents=[self.default_args],
65 help=cmd.__doc__.split('\n', 1)[0],
66 description=cmd.__doc__,
67 formatter_class=argparse.RawDescriptionHelpFormatter,
69 parser.set_defaults(command=cmd)
72 def run(self, **kwargs):
73 """ Parse the command line arguments of the program and execute the
74 appropriate subcommand.
76 args = self.parser.parse_args(args=kwargs.get('cli_args'))
78 if args.subcommand is None:
79 self.parser.print_help()
82 for arg in ('module_dir', 'osm2pgsql_path', 'phplib_dir', 'data_dir', 'phpcgi_path'):
83 setattr(args, arg, Path(kwargs[arg]))
84 args.project_dir = Path(args.project_dir)
86 logging.basicConfig(stream=sys.stderr,
87 format='%(asctime)s: %(message)s',
88 datefmt='%Y-%m-%d %H:%M:%S',
89 level=max(4 - args.verbose, 1) * 10)
91 args.config = Configuration(args.project_dir, args.data_dir / 'settings')
94 return args.command.run(args)
95 except UsageError as exception:
96 log = logging.getLogger()
97 if log.isEnabledFor(logging.DEBUG):
98 raise # use Python's exception printing
99 log.fatal('FATAL: %s', exception)
101 # If we get here, then execution has failed in some way.
105 def _osm2pgsql_options_from_args(args, default_cache, default_threads):
106 """ Set up the stanadrd osm2pgsql from the command line arguments.
108 return dict(osm2pgsql=args.osm2pgsql_path,
109 osm2pgsql_cache=args.osm2pgsql_cache or default_cache,
110 osm2pgsql_style=args.config.get_import_style_file(),
111 threads=args.threads or default_threads,
112 dsn=args.config.get_libpq_dsn(),
113 flatnode_file=args.config.FLATNODE_FILE)
115 ##### Subcommand classes
117 # Each class needs to implement two functions: add_args() adds the CLI parameters
118 # for the subfunction, run() executes the subcommand.
120 # The class documentation doubles as the help text for the command. The
121 # first line is also used in the summary when calling the program without
124 # No need to document the functions each time.
125 # pylint: disable=C0111
126 # Using non-top-level imports to make pyosmium optional for replication only.
127 # pylint: disable=E0012,C0415
132 Create a new Nominatim database from an OSM file.
136 def add_args(parser):
137 group_name = parser.add_argument_group('Required arguments')
138 group = group_name.add_mutually_exclusive_group(required=True)
139 group.add_argument('--osm-file',
140 help='OSM file to be imported.')
141 group.add_argument('--continue', dest='continue_at',
142 choices=['load-data', 'indexing', 'db-postprocess'],
143 help='Continue an import that was interrupted')
144 group = parser.add_argument_group('Optional arguments')
145 group.add_argument('--osm2pgsql-cache', metavar='SIZE', type=int,
146 help='Size of cache to be used by osm2pgsql (in MB)')
147 group.add_argument('--reverse-only', action='store_true',
148 help='Do not create tables and indexes for searching')
149 group.add_argument('--enable-debug-statements', action='store_true',
150 help='Include debug warning statements in SQL code')
151 group.add_argument('--no-partitions', action='store_true',
152 help="""Do not partition search indices
153 (speeds up import of single country extracts)""")
154 group.add_argument('--no-updates', action='store_true',
155 help="""Do not keep tables that are only needed for
156 updating the database later""")
157 group = parser.add_argument_group('Expert options')
158 group.add_argument('--ignore-errors', action='store_true',
159 help='Continue import even when errors in SQL are present')
160 group.add_argument('--index-noanalyse', action='store_true',
161 help='Do not perform analyse operations during index')
166 params = ['setup.php']
168 params.extend(('--all', '--osm-file', args.osm_file))
170 if args.continue_at == 'load-data':
171 params.append('--load-data')
172 if args.continue_at in ('load-data', 'indexing'):
173 params.append('--index')
174 params.extend(('--create-search-indices', '--create-country-names',
176 if args.osm2pgsql_cache:
177 params.extend(('--osm2pgsql-cache', args.osm2pgsql_cache))
178 if args.reverse_only:
179 params.append('--reverse-only')
180 if args.enable_debug_statements:
181 params.append('--enable-debug-statements')
182 if args.no_partitions:
183 params.append('--no-partitions')
185 params.append('--drop')
186 if args.ignore_errors:
187 params.append('--ignore-errors')
188 if args.index_noanalyse:
189 params.append('--index-noanalyse')
191 return run_legacy_script(*params, nominatim_env=args)
196 Make database read-only.
198 About half of data in the Nominatim database is kept only to be able to
199 keep the data up-to-date with new changes made in OpenStreetMap. This
200 command drops all this data and only keeps the part needed for geocoding
203 This command has the same effect as the `--no-updates` option for imports.
207 def add_args(parser):
212 return run_legacy_script('setup.php', '--drop', nominatim_env=args)
215 class SetupSpecialPhrases:
217 Maintain special phrases.
221 def add_args(parser):
222 group = parser.add_argument_group('Input arguments')
223 group.add_argument('--from-wiki', action='store_true',
224 help='Pull special phrases from the OSM wiki.')
225 group = parser.add_argument_group('Output arguments')
226 group.add_argument('-o', '--output', default='-',
227 help="""File to write the preprocessed phrases to.
228 If omitted, it will be written to stdout.""")
232 if args.output != '-':
233 raise NotImplementedError('Only output to stdout is currently implemented.')
234 return run_legacy_script('specialphrases.php', '--wiki-import', nominatim_env=args)
237 class UpdateReplication:
239 Update the database using an online replication service.
243 def add_args(parser):
244 group = parser.add_argument_group('Arguments for initialisation')
245 group.add_argument('--init', action='store_true',
246 help='Initialise the update process')
247 group.add_argument('--no-update-functions', dest='update_functions',
248 action='store_false',
249 help="""Do not update the trigger function to
250 support differential updates.""")
251 group = parser.add_argument_group('Arguments for updates')
252 group.add_argument('--check-for-updates', action='store_true',
253 help='Check if new updates are available and exit')
254 group.add_argument('--once', action='store_true',
255 help="""Download and apply updates only once. When
256 not set, updates are continuously applied""")
257 group.add_argument('--no-index', action='store_false', dest='do_index',
258 help="""Do not index the new data. Only applicable
259 together with --once""")
260 group.add_argument('--osm2pgsql-cache', metavar='SIZE', type=int,
261 help='Size of cache to be used by osm2pgsql (in MB)')
264 def _init_replication(args):
265 from .tools import replication, refresh
267 LOG.warning("Initialising replication updates")
268 conn = connect(args.config.get_libpq_dsn())
269 replication.init_replication(conn, base_url=args.config.REPLICATION_URL)
270 if args.update_functions:
271 LOG.warning("Create functions")
272 refresh.create_functions(conn, args.config, args.data_dir,
279 def _check_for_updates(args):
280 from .tools import replication
282 conn = connect(args.config.get_libpq_dsn())
283 ret = replication.check_for_updates(conn, base_url=args.config.REPLICATION_URL)
288 def _report_update(batchdate, start_import, start_index):
289 def round_time(delta):
290 return dt.timedelta(seconds=int(delta.total_seconds()))
292 end = dt.datetime.now(dt.timezone.utc)
293 LOG.warning("Update completed. Import: %s. %sTotal: %s. Remaining backlog: %s.",
294 round_time((start_index or end) - start_import),
295 "Indexing: {} ".format(round_time(end - start_index))
296 if start_index else '',
297 round_time(end - start_import),
298 round_time(end - batchdate))
302 from .tools import replication
303 from .indexer.indexer import Indexer
305 params = _osm2pgsql_options_from_args(args, 2000, 1)
306 params.update(base_url=args.config.REPLICATION_URL,
307 update_interval=args.config.get_int('REPLICATION_UPDATE_INTERVAL'),
308 import_file=args.project_dir / 'osmosischange.osc',
309 max_diff_size=args.config.get_int('REPLICATION_MAX_DIFF'),
310 indexed_only=not args.once)
312 # Sanity check to not overwhelm the Geofabrik servers.
313 if 'download.geofabrik.de'in params['base_url']\
314 and params['update_interval'] < 86400:
315 LOG.fatal("Update interval too low for download.geofabrik.de.\n"
316 "Please check install documentation "
317 "(https://nominatim.org/release-docs/latest/admin/Import-and-Update#"
318 "setting-up-the-update-process).")
319 raise UsageError("Invalid replication update interval setting.")
322 if not args.do_index:
323 LOG.fatal("Indexing cannot be disabled when running updates continuously.")
324 raise UsageError("Bad argument '--no-index'.")
325 recheck_interval = args.config.get_int('REPLICATION_RECHECK_INTERVAL')
328 conn = connect(args.config.get_libpq_dsn())
329 start = dt.datetime.now(dt.timezone.utc)
330 state = replication.update(conn, params)
331 status.log_status(conn, start, 'import')
332 batchdate, _, _ = status.get_status(conn)
335 if state is not replication.UpdateState.NO_CHANGES and args.do_index:
336 index_start = dt.datetime.now(dt.timezone.utc)
337 indexer = Indexer(args.config.get_libpq_dsn(),
339 indexer.index_boundaries(0, 30)
340 indexer.index_by_rank(0, 30)
342 conn = connect(args.config.get_libpq_dsn())
343 status.set_indexed(conn, True)
344 status.log_status(conn, index_start, 'index')
349 if LOG.isEnabledFor(logging.WARNING):
350 UpdateReplication._report_update(batchdate, start, index_start)
355 if state is replication.UpdateState.NO_CHANGES:
356 LOG.warning("No new changes. Sleeping for %d sec.", recheck_interval)
357 time.sleep(recheck_interval)
364 import osmium # pylint: disable=W0611
365 except ModuleNotFoundError:
366 LOG.fatal("pyosmium not installed. Replication functions not available.\n"
367 "To install pyosmium via pip: pip3 install osmium")
371 return UpdateReplication._init_replication(args)
373 if args.check_for_updates:
374 return UpdateReplication._check_for_updates(args)
376 return UpdateReplication._update(args)
380 Add additional data from a file or an online source.
382 Data is only imported, not indexed. You need to call `nominatim-update index`
383 to complete the process.
387 def add_args(parser):
388 group_name = parser.add_argument_group('Source')
389 group = group_name.add_mutually_exclusive_group(required=True)
390 group.add_argument('--file', metavar='FILE',
391 help='Import data from an OSM file')
392 group.add_argument('--diff', metavar='FILE',
393 help='Import data from an OSM diff file')
394 group.add_argument('--node', metavar='ID', type=int,
395 help='Import a single node from the API')
396 group.add_argument('--way', metavar='ID', type=int,
397 help='Import a single way from the API')
398 group.add_argument('--relation', metavar='ID', type=int,
399 help='Import a single relation from the API')
400 group.add_argument('--tiger-data', metavar='DIR',
401 help='Add housenumbers from the US TIGER census database.')
402 group = parser.add_argument_group('Extra arguments')
403 group.add_argument('--use-main-api', action='store_true',
404 help='Use OSM API instead of Overpass to download objects')
409 os.environ['NOMINATIM_TIGER_DATA_PATH'] = args.tiger_data
410 return run_legacy_script('setup.php', '--import-tiger-data', nominatim_env=args)
412 params = ['update.php']
414 params.extend(('--import-file', args.file))
416 params.extend(('--import-diff', args.diff))
418 params.extend(('--import-node', args.node))
420 params.extend(('--import-way', args.way))
422 params.extend(('--import-relation', args.relation))
423 if args.use_main_api:
424 params.append('--use-main-api')
425 return run_legacy_script(*params, nominatim_env=args)
430 Reindex all new and modified data.
434 def add_args(parser):
435 group = parser.add_argument_group('Filter arguments')
436 group.add_argument('--boundaries-only', action='store_true',
437 help="""Index only administrative boundaries.""")
438 group.add_argument('--no-boundaries', action='store_true',
439 help="""Index everything except administrative boundaries.""")
440 group.add_argument('--minrank', '-r', type=int, metavar='RANK', default=0,
441 help='Minimum/starting rank')
442 group.add_argument('--maxrank', '-R', type=int, metavar='RANK', default=30,
443 help='Maximum/finishing rank')
447 from .indexer.indexer import Indexer
449 indexer = Indexer(args.config.get_libpq_dsn(),
450 args.threads or _num_system_cpus() or 1)
452 if not args.no_boundaries:
453 indexer.index_boundaries(args.minrank, args.maxrank)
454 if not args.boundaries_only:
455 indexer.index_by_rank(args.minrank, args.maxrank)
457 if not args.no_boundaries and not args.boundaries_only \
458 and args.minrank == 0 and args.maxrank == 30:
459 conn = connect(args.config.get_libpq_dsn())
460 status.set_indexed(conn, True)
468 Recompute auxiliary data used by the indexing process.
470 These functions must not be run in parallel with other update commands.
474 def add_args(parser):
475 group = parser.add_argument_group('Data arguments')
476 group.add_argument('--postcodes', action='store_true',
477 help='Update postcode centroid table')
478 group.add_argument('--word-counts', action='store_true',
479 help='Compute frequency of full-word search terms')
480 group.add_argument('--address-levels', action='store_true',
481 help='Reimport address level configuration')
482 group.add_argument('--functions', action='store_true',
483 help='Update the PL/pgSQL functions in the database')
484 group.add_argument('--wiki-data', action='store_true',
485 help='Update Wikipedia/data importance numbers.')
486 group.add_argument('--importance', action='store_true',
487 help='Recompute place importances (expensive!)')
488 group.add_argument('--website', action='store_true',
489 help='Refresh the directory that serves the scripts for the web API')
490 group = parser.add_argument_group('Arguments for function refresh')
491 group.add_argument('--no-diff-updates', action='store_false', dest='diffs',
492 help='Do not enable code for propagating updates')
493 group.add_argument('--enable-debug-statements', action='store_true',
494 help='Enable debug warning statements in functions')
498 from .tools import refresh
501 LOG.warning("Update postcodes centroid")
502 conn = connect(args.config.get_libpq_dsn())
503 refresh.update_postcodes(conn, args.data_dir)
507 LOG.warning('Recompute frequency of full-word search terms')
508 conn = connect(args.config.get_libpq_dsn())
509 refresh.recompute_word_counts(conn, args.data_dir)
512 if args.address_levels:
513 cfg = Path(args.config.ADDRESS_LEVEL_CONFIG)
514 LOG.warning('Updating address levels from %s', cfg)
515 conn = connect(args.config.get_libpq_dsn())
516 refresh.load_address_levels_from_file(conn, cfg)
520 LOG.warning('Create functions')
521 conn = connect(args.config.get_libpq_dsn())
522 refresh.create_functions(conn, args.config, args.data_dir,
523 args.diffs, args.enable_debug_statements)
527 run_legacy_script('setup.php', '--import-wikipedia-articles',
528 nominatim_env=args, throw_on_fail=True)
529 # Attention: importance MUST come after wiki data import.
531 run_legacy_script('update.php', '--recompute-importance',
532 nominatim_env=args, throw_on_fail=True)
534 run_legacy_script('setup.php', '--setup-website',
535 nominatim_env=args, throw_on_fail=True)
540 class AdminCheckDatabase:
542 Check that the database is complete and operational.
546 def add_args(parser):
551 return run_legacy_script('check_import_finished.php', nominatim_env=args)
556 Warm database caches for search and reverse queries.
560 def add_args(parser):
561 group = parser.add_argument_group('Target arguments')
562 group.add_argument('--search-only', action='store_const', dest='target',
564 help="Only pre-warm tables for search queries")
565 group.add_argument('--reverse-only', action='store_const', dest='target',
567 help="Only pre-warm tables for reverse queries")
571 params = ['warm.php']
572 if args.target == 'reverse':
573 params.append('--reverse-only')
574 if args.target == 'search':
575 params.append('--search-only')
576 return run_legacy_script(*params, nominatim_env=args)
581 Export addresses as CSV file from the database.
585 def add_args(parser):
586 group = parser.add_argument_group('Output arguments')
587 group.add_argument('--output-type', default='street',
588 choices=('continent', 'country', 'state', 'county',
589 'city', 'suburb', 'street', 'path'),
590 help='Type of places to output (default: street)')
591 group.add_argument('--output-format',
592 default='street;suburb;city;county;state;country',
593 help="""Semicolon-separated list of address types
594 (see --output-type). Multiple ranks can be
595 merged into one column by simply using a
596 comma-separated list.""")
597 group.add_argument('--output-all-postcodes', action='store_true',
598 help="""List all postcodes for address instead of
599 just the most likely one""")
600 group.add_argument('--language',
601 help="""Preferred language for output
602 (use local name, if omitted)""")
603 group = parser.add_argument_group('Filter arguments')
604 group.add_argument('--restrict-to-country', metavar='COUNTRY_CODE',
605 help='Export only objects within country')
606 group.add_argument('--restrict-to-osm-node', metavar='ID', type=int,
607 help='Export only children of this OSM node')
608 group.add_argument('--restrict-to-osm-way', metavar='ID', type=int,
609 help='Export only children of this OSM way')
610 group.add_argument('--restrict-to-osm-relation', metavar='ID', type=int,
611 help='Export only children of this OSM relation')
616 params = ['export.php',
617 '--output-type', args.output_type,
618 '--output-format', args.output_format]
619 if args.output_all_postcodes:
620 params.append('--output-all-postcodes')
622 params.extend(('--language', args.language))
623 if args.restrict_to_country:
624 params.extend(('--restrict-to-country', args.restrict_to_country))
625 if args.restrict_to_osm_node:
626 params.extend(('--restrict-to-osm-node', args.restrict_to_osm_node))
627 if args.restrict_to_osm_way:
628 params.extend(('--restrict-to-osm-way', args.restrict_to_osm_way))
629 if args.restrict_to_osm_relation:
630 params.extend(('--restrict-to-osm-relation', args.restrict_to_osm_relation))
632 return run_legacy_script(*params, nominatim_env=args)
635 ('street', 'housenumber and street'),
636 ('city', 'city, town or village'),
637 ('county', 'county'),
639 ('country', 'country'),
640 ('postalcode', 'postcode')
644 ('addressdetails', 'Include a breakdown of the address into elements.'),
645 ('extratags', """Include additional information if available
646 (e.g. wikipedia link, opening hours)."""),
647 ('namedetails', 'Include a list of alternative names.')
651 ('addressdetails', 'Include a breakdown of the address into elements.'),
652 ('keywords', 'Include a list of name keywords and address keywords.'),
653 ('linkedplaces', 'Include a details of places that are linked with this one.'),
654 ('hierarchy', 'Include details of places lower in the address hierarchy.'),
655 ('group_hierarchy', 'Group the places by type.'),
656 ('polygon_geojson', 'Include geometry of result.')
659 def _add_api_output_arguments(parser):
660 group = parser.add_argument_group('Output arguments')
661 group.add_argument('--format', default='jsonv2',
662 choices=['xml', 'json', 'jsonv2', 'geojson', 'geocodejson'],
663 help='Format of result')
664 for name, desc in EXTRADATA_PARAMS:
665 group.add_argument('--' + name, action='store_true', help=desc)
667 group.add_argument('--lang', '--accept-language', metavar='LANGS',
668 help='Preferred language order for presenting search results')
669 group.add_argument('--polygon-output',
670 choices=['geojson', 'kml', 'svg', 'text'],
671 help='Output geometry of results as a GeoJSON, KML, SVG or WKT.')
672 group.add_argument('--polygon-threshold', type=float, metavar='TOLERANCE',
673 help="""Simplify output geometry.
674 Parameter is difference tolerance in degrees.""")
679 Execute API search query.
683 def add_args(parser):
684 group = parser.add_argument_group('Query arguments')
685 group.add_argument('--query',
686 help='Free-form query string')
687 for name, desc in STRUCTURED_QUERY:
688 group.add_argument('--' + name, help='Structured query: ' + desc)
690 _add_api_output_arguments(parser)
692 group = parser.add_argument_group('Result limitation')
693 group.add_argument('--countrycodes', metavar='CC,..',
694 help='Limit search results to one or more countries.')
695 group.add_argument('--exclude_place_ids', metavar='ID,..',
696 help='List of search object to be excluded')
697 group.add_argument('--limit', type=int,
698 help='Limit the number of returned results')
699 group.add_argument('--viewbox', metavar='X1,Y1,X2,Y2',
700 help='Preferred area to find search results')
701 group.add_argument('--bounded', action='store_true',
702 help='Strictly restrict results to viewbox area')
704 group = parser.add_argument_group('Other arguments')
705 group.add_argument('--no-dedupe', action='store_false', dest='dedupe',
706 help='Do not remove duplicates from the result list')
712 params = dict(q=args.query)
714 params = {k : getattr(args, k) for k, _ in STRUCTURED_QUERY if getattr(args, k)}
716 for param, _ in EXTRADATA_PARAMS:
717 if getattr(args, param):
719 for param in ('format', 'countrycodes', 'exclude_place_ids', 'limit', 'viewbox'):
720 if getattr(args, param):
721 params[param] = getattr(args, param)
723 params['accept-language'] = args.lang
724 if args.polygon_output:
725 params['polygon_' + args.polygon_output] = '1'
726 if args.polygon_threshold:
727 params['polygon_threshold'] = args.polygon_threshold
729 params['bounded'] = '1'
731 params['dedupe'] = '0'
733 return run_api_script('search', args.project_dir,
734 phpcgi_bin=args.phpcgi_path, params=params)
738 Execute API reverse query.
742 def add_args(parser):
743 group = parser.add_argument_group('Query arguments')
744 group.add_argument('--lat', type=float, required=True,
745 help='Latitude of coordinate to look up (in WGS84)')
746 group.add_argument('--lon', type=float, required=True,
747 help='Longitude of coordinate to look up (in WGS84)')
748 group.add_argument('--zoom', type=int,
749 help='Level of detail required for the address')
751 _add_api_output_arguments(parser)
756 params = dict(lat=args.lat, lon=args.lon)
757 if args.zoom is not None:
758 params['zoom'] = args.zoom
760 for param, _ in EXTRADATA_PARAMS:
761 if getattr(args, param):
764 params['format'] = args.format
766 params['accept-language'] = args.lang
767 if args.polygon_output:
768 params['polygon_' + args.polygon_output] = '1'
769 if args.polygon_threshold:
770 params['polygon_threshold'] = args.polygon_threshold
772 return run_api_script('reverse', args.project_dir,
773 phpcgi_bin=args.phpcgi_path, params=params)
778 Execute API reverse query.
782 def add_args(parser):
783 group = parser.add_argument_group('Query arguments')
784 group.add_argument('--id', metavar='OSMID',
785 action='append', required=True, dest='ids',
786 help='OSM id to lookup in format <NRW><id> (may be repeated)')
788 _add_api_output_arguments(parser)
793 params = dict(osm_ids=','.join(args.ids))
795 for param, _ in EXTRADATA_PARAMS:
796 if getattr(args, param):
799 params['format'] = args.format
801 params['accept-language'] = args.lang
802 if args.polygon_output:
803 params['polygon_' + args.polygon_output] = '1'
804 if args.polygon_threshold:
805 params['polygon_threshold'] = args.polygon_threshold
807 return run_api_script('lookup', args.project_dir,
808 phpcgi_bin=args.phpcgi_path, params=params)
813 Execute API lookup query.
817 def add_args(parser):
818 group = parser.add_argument_group('Query arguments')
819 objs = group.add_mutually_exclusive_group(required=True)
820 objs.add_argument('--node', '-n', type=int,
821 help="Look up the OSM node with the given ID.")
822 objs.add_argument('--way', '-w', type=int,
823 help="Look up the OSM way with the given ID.")
824 objs.add_argument('--relation', '-r', type=int,
825 help="Look up the OSM relation with the given ID.")
826 objs.add_argument('--place_id', '-p', type=int,
827 help='Database internal identifier of the OSM object to look up.')
828 group.add_argument('--class', dest='object_class',
829 help="""Class type to disambiguated multiple entries
830 of the same object.""")
832 group = parser.add_argument_group('Output arguments')
833 for name, desc in DETAILS_SWITCHES:
834 group.add_argument('--' + name, action='store_true', help=desc)
835 group.add_argument('--lang', '--accept-language', metavar='LANGS',
836 help='Preferred language order for presenting search results')
841 params = dict(osmtype='N', osmid=args.node)
843 params = dict(osmtype='W', osmid=args.node)
845 params = dict(osmtype='R', osmid=args.node)
847 params = dict(place_id=args.place_id)
848 if args.object_class:
849 params['class'] = args.object_class
850 for name, _ in DETAILS_SWITCHES:
851 params[name] = '1' if getattr(args, name) else '0'
853 return run_api_script('details', args.project_dir,
854 phpcgi_bin=args.phpcgi_path, params=params)
859 Execute API status query.
863 def add_args(parser):
864 group = parser.add_argument_group('API parameters')
865 group.add_argument('--format', default='text', choices=['text', 'json'],
866 help='Format of result')
870 return run_api_script('status', args.project_dir,
871 phpcgi_bin=args.phpcgi_path,
872 params=dict(format=args.format))
875 def nominatim(**kwargs):
877 Command-line tools for importing, updating, administrating and
878 querying the Nominatim database.
880 parser = CommandlineParser('nominatim', nominatim.__doc__)
882 parser.add_subcommand('import', SetupAll)
883 parser.add_subcommand('freeze', SetupFreeze)
884 parser.add_subcommand('replication', UpdateReplication)
886 parser.add_subcommand('check-database', AdminCheckDatabase)
887 parser.add_subcommand('warm', AdminWarm)
889 parser.add_subcommand('special-phrases', SetupSpecialPhrases)
891 parser.add_subcommand('add-data', UpdateAddData)
892 parser.add_subcommand('index', UpdateIndex)
893 parser.add_subcommand('refresh', UpdateRefresh)
895 parser.add_subcommand('export', QueryExport)
897 if kwargs.get('phpcgi_path'):
898 parser.add_subcommand('search', APISearch)
899 parser.add_subcommand('reverse', APIReverse)
900 parser.add_subcommand('lookup', APILookup)
901 parser.add_subcommand('details', APIDetails)
902 parser.add_subcommand('status', APIStatus)
904 parser.parser.epilog = 'php-cgi not found. Query commands not available.'
906 return parser.run(**kwargs)