2 Command-line interface to the Nominatim functions for import, update,
3 database administration and querying.
12 from pathlib import Path
14 from .config import Configuration
15 from .tools.exec_utils import run_legacy_script, run_api_script, run_php_server
16 from .db.connection import connect
17 from .db import status
18 from .errors import UsageError
20 LOG = logging.getLogger()
22 def _num_system_cpus():
24 cpus = len(os.sched_getaffinity(0))
25 except NotImplementedError:
28 return cpus or os.cpu_count()
31 class CommandlineParser:
32 """ Wraps some of the common functions for parsing the command line
33 and setting up subcommands.
35 def __init__(self, prog, description):
36 self.parser = argparse.ArgumentParser(
38 description=description,
39 formatter_class=argparse.RawDescriptionHelpFormatter)
41 self.subs = self.parser.add_subparsers(title='available commands',
44 # Arguments added to every sub-command
45 self.default_args = argparse.ArgumentParser(add_help=False)
46 group = self.default_args.add_argument_group('Default arguments')
47 group.add_argument('-h', '--help', action='help',
48 help='Show this help message and exit')
49 group.add_argument('-q', '--quiet', action='store_const', const=0,
50 dest='verbose', default=1,
51 help='Print only error messages')
52 group.add_argument('-v', '--verbose', action='count', default=1,
53 help='Increase verboseness of output')
54 group.add_argument('--project-dir', metavar='DIR', default='.',
55 help='Base directory of the Nominatim installation (default:.)')
56 group.add_argument('-j', '--threads', metavar='NUM', type=int,
57 help='Number of parallel threads to use')
60 def add_subcommand(self, name, cmd):
61 """ Add a subcommand to the parser. The subcommand must be a class
62 with a function add_args() that adds the parameters for the
63 subcommand and a run() function that executes the command.
65 parser = self.subs.add_parser(name, parents=[self.default_args],
66 help=cmd.__doc__.split('\n', 1)[0],
67 description=cmd.__doc__,
68 formatter_class=argparse.RawDescriptionHelpFormatter,
70 parser.set_defaults(command=cmd)
73 def run(self, **kwargs):
74 """ Parse the command line arguments of the program and execute the
75 appropriate subcommand.
77 args = self.parser.parse_args(args=kwargs.get('cli_args'))
79 if args.subcommand is None:
80 self.parser.print_help()
83 for arg in ('module_dir', 'osm2pgsql_path', 'phplib_dir', 'data_dir', 'phpcgi_path'):
84 setattr(args, arg, Path(kwargs[arg]))
85 args.project_dir = Path(args.project_dir).resolve()
87 logging.basicConfig(stream=sys.stderr,
88 format='%(asctime)s: %(message)s',
89 datefmt='%Y-%m-%d %H:%M:%S',
90 level=max(4 - args.verbose, 1) * 10)
92 args.config = Configuration(args.project_dir, args.data_dir / 'settings')
94 log = logging.getLogger()
95 log.warning('Using project directory: %s', str(args.project_dir))
98 return args.command.run(args)
99 except UsageError as exception:
100 if log.isEnabledFor(logging.DEBUG):
101 raise # use Python's exception printing
102 log.fatal('FATAL: %s', exception)
104 # If we get here, then execution has failed in some way.
108 def _osm2pgsql_options_from_args(args, default_cache, default_threads):
109 """ Set up the stanadrd osm2pgsql from the command line arguments.
111 return dict(osm2pgsql=args.osm2pgsql_path,
112 osm2pgsql_cache=args.osm2pgsql_cache or default_cache,
113 osm2pgsql_style=args.config.get_import_style_file(),
114 threads=args.threads or default_threads,
115 dsn=args.config.get_libpq_dsn(),
116 flatnode_file=args.config.FLATNODE_FILE)
118 ##### Subcommand classes
120 # Each class needs to implement two functions: add_args() adds the CLI parameters
121 # for the subfunction, run() executes the subcommand.
123 # The class documentation doubles as the help text for the command. The
124 # first line is also used in the summary when calling the program without
127 # No need to document the functions each time.
128 # pylint: disable=C0111
129 # Using non-top-level imports to make pyosmium optional for replication only.
130 # pylint: disable=E0012,C0415
135 Create a new Nominatim database from an OSM file.
139 def add_args(parser):
140 group_name = parser.add_argument_group('Required arguments')
141 group = group_name.add_mutually_exclusive_group(required=True)
142 group.add_argument('--osm-file',
143 help='OSM file to be imported.')
144 group.add_argument('--continue', dest='continue_at',
145 choices=['load-data', 'indexing', 'db-postprocess'],
146 help='Continue an import that was interrupted')
147 group = parser.add_argument_group('Optional arguments')
148 group.add_argument('--osm2pgsql-cache', metavar='SIZE', type=int,
149 help='Size of cache to be used by osm2pgsql (in MB)')
150 group.add_argument('--reverse-only', action='store_true',
151 help='Do not create tables and indexes for searching')
152 group.add_argument('--enable-debug-statements', action='store_true',
153 help='Include debug warning statements in SQL code')
154 group.add_argument('--no-partitions', action='store_true',
155 help="""Do not partition search indices
156 (speeds up import of single country extracts)""")
157 group.add_argument('--no-updates', action='store_true',
158 help="""Do not keep tables that are only needed for
159 updating the database later""")
160 group = parser.add_argument_group('Expert options')
161 group.add_argument('--ignore-errors', action='store_true',
162 help='Continue import even when errors in SQL are present')
163 group.add_argument('--index-noanalyse', action='store_true',
164 help='Do not perform analyse operations during index')
169 params = ['setup.php']
171 params.extend(('--all', '--osm-file', args.osm_file))
173 if args.continue_at == 'load-data':
174 params.append('--load-data')
175 if args.continue_at in ('load-data', 'indexing'):
176 params.append('--index')
177 params.extend(('--create-search-indices', '--create-country-names',
179 if args.osm2pgsql_cache:
180 params.extend(('--osm2pgsql-cache', args.osm2pgsql_cache))
181 if args.reverse_only:
182 params.append('--reverse-only')
183 if args.enable_debug_statements:
184 params.append('--enable-debug-statements')
185 if args.no_partitions:
186 params.append('--no-partitions')
188 params.append('--drop')
189 if args.ignore_errors:
190 params.append('--ignore-errors')
191 if args.index_noanalyse:
192 params.append('--index-noanalyse')
194 return run_legacy_script(*params, nominatim_env=args)
199 Make database read-only.
201 About half of data in the Nominatim database is kept only to be able to
202 keep the data up-to-date with new changes made in OpenStreetMap. This
203 command drops all this data and only keeps the part needed for geocoding
206 This command has the same effect as the `--no-updates` option for imports.
210 def add_args(parser):
215 return run_legacy_script('setup.php', '--drop', nominatim_env=args)
218 class SetupSpecialPhrases:
220 Maintain special phrases.
224 def add_args(parser):
225 group = parser.add_argument_group('Input arguments')
226 group.add_argument('--from-wiki', action='store_true',
227 help='Pull special phrases from the OSM wiki.')
228 group = parser.add_argument_group('Output arguments')
229 group.add_argument('-o', '--output', default='-',
230 help="""File to write the preprocessed phrases to.
231 If omitted, it will be written to stdout.""")
235 if args.output != '-':
236 raise NotImplementedError('Only output to stdout is currently implemented.')
237 return run_legacy_script('specialphrases.php', '--wiki-import', nominatim_env=args)
240 class UpdateReplication:
242 Update the database using an online replication service.
246 def add_args(parser):
247 group = parser.add_argument_group('Arguments for initialisation')
248 group.add_argument('--init', action='store_true',
249 help='Initialise the update process')
250 group.add_argument('--no-update-functions', dest='update_functions',
251 action='store_false',
252 help="""Do not update the trigger function to
253 support differential updates.""")
254 group = parser.add_argument_group('Arguments for updates')
255 group.add_argument('--check-for-updates', action='store_true',
256 help='Check if new updates are available and exit')
257 group.add_argument('--once', action='store_true',
258 help="""Download and apply updates only once. When
259 not set, updates are continuously applied""")
260 group.add_argument('--no-index', action='store_false', dest='do_index',
261 help="""Do not index the new data. Only applicable
262 together with --once""")
263 group.add_argument('--osm2pgsql-cache', metavar='SIZE', type=int,
264 help='Size of cache to be used by osm2pgsql (in MB)')
265 group = parser.add_argument_group('Download parameters')
266 group.add_argument('--socket-timeout', dest='socket_timeout', type=int, default=60,
267 help='Set timeout for file downloads.')
270 def _init_replication(args):
271 from .tools import replication, refresh
273 socket.setdefaulttimeout(args.socket_timeout)
275 LOG.warning("Initialising replication updates")
276 conn = connect(args.config.get_libpq_dsn())
277 replication.init_replication(conn, base_url=args.config.REPLICATION_URL)
278 if args.update_functions:
279 LOG.warning("Create functions")
280 refresh.create_functions(conn, args.config, args.data_dir,
287 def _check_for_updates(args):
288 from .tools import replication
290 conn = connect(args.config.get_libpq_dsn())
291 ret = replication.check_for_updates(conn, base_url=args.config.REPLICATION_URL)
296 def _report_update(batchdate, start_import, start_index):
297 def round_time(delta):
298 return dt.timedelta(seconds=int(delta.total_seconds()))
300 end = dt.datetime.now(dt.timezone.utc)
301 LOG.warning("Update completed. Import: %s. %sTotal: %s. Remaining backlog: %s.",
302 round_time((start_index or end) - start_import),
303 "Indexing: {} ".format(round_time(end - start_index))
304 if start_index else '',
305 round_time(end - start_import),
306 round_time(end - batchdate))
310 from .tools import replication
311 from .indexer.indexer import Indexer
313 params = _osm2pgsql_options_from_args(args, 2000, 1)
314 params.update(base_url=args.config.REPLICATION_URL,
315 update_interval=args.config.get_int('REPLICATION_UPDATE_INTERVAL'),
316 import_file=args.project_dir / 'osmosischange.osc',
317 max_diff_size=args.config.get_int('REPLICATION_MAX_DIFF'),
318 indexed_only=not args.once)
320 # Sanity check to not overwhelm the Geofabrik servers.
321 if 'download.geofabrik.de'in params['base_url']\
322 and params['update_interval'] < 86400:
323 LOG.fatal("Update interval too low for download.geofabrik.de.\n"
324 "Please check install documentation "
325 "(https://nominatim.org/release-docs/latest/admin/Import-and-Update#"
326 "setting-up-the-update-process).")
327 raise UsageError("Invalid replication update interval setting.")
330 if not args.do_index:
331 LOG.fatal("Indexing cannot be disabled when running updates continuously.")
332 raise UsageError("Bad argument '--no-index'.")
333 recheck_interval = args.config.get_int('REPLICATION_RECHECK_INTERVAL')
336 conn = connect(args.config.get_libpq_dsn())
337 start = dt.datetime.now(dt.timezone.utc)
338 state = replication.update(conn, params)
339 if state is not replication.UpdateState.NO_CHANGES:
340 status.log_status(conn, start, 'import')
341 batchdate, _, _ = status.get_status(conn)
344 if state is not replication.UpdateState.NO_CHANGES and args.do_index:
345 index_start = dt.datetime.now(dt.timezone.utc)
346 indexer = Indexer(args.config.get_libpq_dsn(),
348 indexer.index_boundaries(0, 30)
349 indexer.index_by_rank(0, 30)
351 conn = connect(args.config.get_libpq_dsn())
352 status.set_indexed(conn, True)
353 status.log_status(conn, index_start, 'index')
358 if LOG.isEnabledFor(logging.WARNING):
359 UpdateReplication._report_update(batchdate, start, index_start)
364 if state is replication.UpdateState.NO_CHANGES:
365 LOG.warning("No new changes. Sleeping for %d sec.", recheck_interval)
366 time.sleep(recheck_interval)
373 import osmium # pylint: disable=W0611
374 except ModuleNotFoundError:
375 LOG.fatal("pyosmium not installed. Replication functions not available.\n"
376 "To install pyosmium via pip: pip3 install osmium")
380 return UpdateReplication._init_replication(args)
382 if args.check_for_updates:
383 return UpdateReplication._check_for_updates(args)
385 return UpdateReplication._update(args)
389 Add additional data from a file or an online source.
391 Data is only imported, not indexed. You need to call `nominatim-update index`
392 to complete the process.
396 def add_args(parser):
397 group_name = parser.add_argument_group('Source')
398 group = group_name.add_mutually_exclusive_group(required=True)
399 group.add_argument('--file', metavar='FILE',
400 help='Import data from an OSM file')
401 group.add_argument('--diff', metavar='FILE',
402 help='Import data from an OSM diff file')
403 group.add_argument('--node', metavar='ID', type=int,
404 help='Import a single node from the API')
405 group.add_argument('--way', metavar='ID', type=int,
406 help='Import a single way from the API')
407 group.add_argument('--relation', metavar='ID', type=int,
408 help='Import a single relation from the API')
409 group.add_argument('--tiger-data', metavar='DIR',
410 help='Add housenumbers from the US TIGER census database.')
411 group = parser.add_argument_group('Extra arguments')
412 group.add_argument('--use-main-api', action='store_true',
413 help='Use OSM API instead of Overpass to download objects')
418 os.environ['NOMINATIM_TIGER_DATA_PATH'] = args.tiger_data
419 return run_legacy_script('setup.php', '--import-tiger-data', nominatim_env=args)
421 params = ['update.php']
423 params.extend(('--import-file', args.file))
425 params.extend(('--import-diff', args.diff))
427 params.extend(('--import-node', args.node))
429 params.extend(('--import-way', args.way))
431 params.extend(('--import-relation', args.relation))
432 if args.use_main_api:
433 params.append('--use-main-api')
434 return run_legacy_script(*params, nominatim_env=args)
439 Reindex all new and modified data.
443 def add_args(parser):
444 group = parser.add_argument_group('Filter arguments')
445 group.add_argument('--boundaries-only', action='store_true',
446 help="""Index only administrative boundaries.""")
447 group.add_argument('--no-boundaries', action='store_true',
448 help="""Index everything except administrative boundaries.""")
449 group.add_argument('--minrank', '-r', type=int, metavar='RANK', default=0,
450 help='Minimum/starting rank')
451 group.add_argument('--maxrank', '-R', type=int, metavar='RANK', default=30,
452 help='Maximum/finishing rank')
456 from .indexer.indexer import Indexer
458 indexer = Indexer(args.config.get_libpq_dsn(),
459 args.threads or _num_system_cpus() or 1)
461 if not args.no_boundaries:
462 indexer.index_boundaries(args.minrank, args.maxrank)
463 if not args.boundaries_only:
464 indexer.index_by_rank(args.minrank, args.maxrank)
466 if not args.no_boundaries and not args.boundaries_only \
467 and args.minrank == 0 and args.maxrank == 30:
468 conn = connect(args.config.get_libpq_dsn())
469 status.set_indexed(conn, True)
477 Recompute auxiliary data used by the indexing process.
479 These functions must not be run in parallel with other update commands.
483 def add_args(parser):
484 group = parser.add_argument_group('Data arguments')
485 group.add_argument('--postcodes', action='store_true',
486 help='Update postcode centroid table')
487 group.add_argument('--word-counts', action='store_true',
488 help='Compute frequency of full-word search terms')
489 group.add_argument('--address-levels', action='store_true',
490 help='Reimport address level configuration')
491 group.add_argument('--functions', action='store_true',
492 help='Update the PL/pgSQL functions in the database')
493 group.add_argument('--wiki-data', action='store_true',
494 help='Update Wikipedia/data importance numbers.')
495 group.add_argument('--importance', action='store_true',
496 help='Recompute place importances (expensive!)')
497 group.add_argument('--website', action='store_true',
498 help='Refresh the directory that serves the scripts for the web API')
499 group = parser.add_argument_group('Arguments for function refresh')
500 group.add_argument('--no-diff-updates', action='store_false', dest='diffs',
501 help='Do not enable code for propagating updates')
502 group.add_argument('--enable-debug-statements', action='store_true',
503 help='Enable debug warning statements in functions')
507 from .tools import refresh
510 LOG.warning("Update postcodes centroid")
511 conn = connect(args.config.get_libpq_dsn())
512 refresh.update_postcodes(conn, args.data_dir)
516 LOG.warning('Recompute frequency of full-word search terms')
517 conn = connect(args.config.get_libpq_dsn())
518 refresh.recompute_word_counts(conn, args.data_dir)
521 if args.address_levels:
522 cfg = Path(args.config.ADDRESS_LEVEL_CONFIG)
523 LOG.warning('Updating address levels from %s', cfg)
524 conn = connect(args.config.get_libpq_dsn())
525 refresh.load_address_levels_from_file(conn, cfg)
529 LOG.warning('Create functions')
530 conn = connect(args.config.get_libpq_dsn())
531 refresh.create_functions(conn, args.config, args.data_dir,
532 args.diffs, args.enable_debug_statements)
536 run_legacy_script('setup.php', '--import-wikipedia-articles',
537 nominatim_env=args, throw_on_fail=True)
538 # Attention: importance MUST come after wiki data import.
540 run_legacy_script('update.php', '--recompute-importance',
541 nominatim_env=args, throw_on_fail=True)
543 run_legacy_script('setup.php', '--setup-website',
544 nominatim_env=args, throw_on_fail=True)
549 class AdminCheckDatabase:
551 Check that the database is complete and operational.
555 def add_args(parser):
560 return run_legacy_script('check_import_finished.php', nominatim_env=args)
565 Warm database caches for search and reverse queries.
569 def add_args(parser):
570 group = parser.add_argument_group('Target arguments')
571 group.add_argument('--search-only', action='store_const', dest='target',
573 help="Only pre-warm tables for search queries")
574 group.add_argument('--reverse-only', action='store_const', dest='target',
576 help="Only pre-warm tables for reverse queries")
580 params = ['warm.php']
581 if args.target == 'reverse':
582 params.append('--reverse-only')
583 if args.target == 'search':
584 params.append('--search-only')
585 return run_legacy_script(*params, nominatim_env=args)
590 Export addresses as CSV file from the database.
594 def add_args(parser):
595 group = parser.add_argument_group('Output arguments')
596 group.add_argument('--output-type', default='street',
597 choices=('continent', 'country', 'state', 'county',
598 'city', 'suburb', 'street', 'path'),
599 help='Type of places to output (default: street)')
600 group.add_argument('--output-format',
601 default='street;suburb;city;county;state;country',
602 help="""Semicolon-separated list of address types
603 (see --output-type). Multiple ranks can be
604 merged into one column by simply using a
605 comma-separated list.""")
606 group.add_argument('--output-all-postcodes', action='store_true',
607 help="""List all postcodes for address instead of
608 just the most likely one""")
609 group.add_argument('--language',
610 help="""Preferred language for output
611 (use local name, if omitted)""")
612 group = parser.add_argument_group('Filter arguments')
613 group.add_argument('--restrict-to-country', metavar='COUNTRY_CODE',
614 help='Export only objects within country')
615 group.add_argument('--restrict-to-osm-node', metavar='ID', type=int,
616 help='Export only children of this OSM node')
617 group.add_argument('--restrict-to-osm-way', metavar='ID', type=int,
618 help='Export only children of this OSM way')
619 group.add_argument('--restrict-to-osm-relation', metavar='ID', type=int,
620 help='Export only children of this OSM relation')
625 params = ['export.php',
626 '--output-type', args.output_type,
627 '--output-format', args.output_format]
628 if args.output_all_postcodes:
629 params.append('--output-all-postcodes')
631 params.extend(('--language', args.language))
632 if args.restrict_to_country:
633 params.extend(('--restrict-to-country', args.restrict_to_country))
634 if args.restrict_to_osm_node:
635 params.extend(('--restrict-to-osm-node', args.restrict_to_osm_node))
636 if args.restrict_to_osm_way:
637 params.extend(('--restrict-to-osm-way', args.restrict_to_osm_way))
638 if args.restrict_to_osm_relation:
639 params.extend(('--restrict-to-osm-relation', args.restrict_to_osm_relation))
641 return run_legacy_script(*params, nominatim_env=args)
646 Start a simple web server for serving the API.
648 This command starts the built-in PHP webserver to serve the website
649 from the current project directory. This webserver is only suitable
650 for testing and develop. Do not use it in production setups!
652 By the default, the webserver can be accessed at: http://127.0.0.1:8088
656 def add_args(parser):
657 group = parser.add_argument_group('Server arguments')
658 group.add_argument('--server', default='127.0.0.1:8088',
659 help='The address the server will listen to.')
663 run_php_server(args.server, args.project_dir / 'website')
666 ('street', 'housenumber and street'),
667 ('city', 'city, town or village'),
668 ('county', 'county'),
670 ('country', 'country'),
671 ('postalcode', 'postcode')
675 ('addressdetails', 'Include a breakdown of the address into elements.'),
676 ('extratags', """Include additional information if available
677 (e.g. wikipedia link, opening hours)."""),
678 ('namedetails', 'Include a list of alternative names.')
682 ('addressdetails', 'Include a breakdown of the address into elements.'),
683 ('keywords', 'Include a list of name keywords and address keywords.'),
684 ('linkedplaces', 'Include a details of places that are linked with this one.'),
685 ('hierarchy', 'Include details of places lower in the address hierarchy.'),
686 ('group_hierarchy', 'Group the places by type.'),
687 ('polygon_geojson', 'Include geometry of result.')
690 def _add_api_output_arguments(parser):
691 group = parser.add_argument_group('Output arguments')
692 group.add_argument('--format', default='jsonv2',
693 choices=['xml', 'json', 'jsonv2', 'geojson', 'geocodejson'],
694 help='Format of result')
695 for name, desc in EXTRADATA_PARAMS:
696 group.add_argument('--' + name, action='store_true', help=desc)
698 group.add_argument('--lang', '--accept-language', metavar='LANGS',
699 help='Preferred language order for presenting search results')
700 group.add_argument('--polygon-output',
701 choices=['geojson', 'kml', 'svg', 'text'],
702 help='Output geometry of results as a GeoJSON, KML, SVG or WKT.')
703 group.add_argument('--polygon-threshold', type=float, metavar='TOLERANCE',
704 help="""Simplify output geometry.
705 Parameter is difference tolerance in degrees.""")
710 Execute API search query.
714 def add_args(parser):
715 group = parser.add_argument_group('Query arguments')
716 group.add_argument('--query',
717 help='Free-form query string')
718 for name, desc in STRUCTURED_QUERY:
719 group.add_argument('--' + name, help='Structured query: ' + desc)
721 _add_api_output_arguments(parser)
723 group = parser.add_argument_group('Result limitation')
724 group.add_argument('--countrycodes', metavar='CC,..',
725 help='Limit search results to one or more countries.')
726 group.add_argument('--exclude_place_ids', metavar='ID,..',
727 help='List of search object to be excluded')
728 group.add_argument('--limit', type=int,
729 help='Limit the number of returned results')
730 group.add_argument('--viewbox', metavar='X1,Y1,X2,Y2',
731 help='Preferred area to find search results')
732 group.add_argument('--bounded', action='store_true',
733 help='Strictly restrict results to viewbox area')
735 group = parser.add_argument_group('Other arguments')
736 group.add_argument('--no-dedupe', action='store_false', dest='dedupe',
737 help='Do not remove duplicates from the result list')
743 params = dict(q=args.query)
745 params = {k : getattr(args, k) for k, _ in STRUCTURED_QUERY if getattr(args, k)}
747 for param, _ in EXTRADATA_PARAMS:
748 if getattr(args, param):
750 for param in ('format', 'countrycodes', 'exclude_place_ids', 'limit', 'viewbox'):
751 if getattr(args, param):
752 params[param] = getattr(args, param)
754 params['accept-language'] = args.lang
755 if args.polygon_output:
756 params['polygon_' + args.polygon_output] = '1'
757 if args.polygon_threshold:
758 params['polygon_threshold'] = args.polygon_threshold
760 params['bounded'] = '1'
762 params['dedupe'] = '0'
764 return run_api_script('search', args.project_dir,
765 phpcgi_bin=args.phpcgi_path, params=params)
769 Execute API reverse query.
773 def add_args(parser):
774 group = parser.add_argument_group('Query arguments')
775 group.add_argument('--lat', type=float, required=True,
776 help='Latitude of coordinate to look up (in WGS84)')
777 group.add_argument('--lon', type=float, required=True,
778 help='Longitude of coordinate to look up (in WGS84)')
779 group.add_argument('--zoom', type=int,
780 help='Level of detail required for the address')
782 _add_api_output_arguments(parser)
787 params = dict(lat=args.lat, lon=args.lon)
788 if args.zoom is not None:
789 params['zoom'] = args.zoom
791 for param, _ in EXTRADATA_PARAMS:
792 if getattr(args, param):
795 params['format'] = args.format
797 params['accept-language'] = args.lang
798 if args.polygon_output:
799 params['polygon_' + args.polygon_output] = '1'
800 if args.polygon_threshold:
801 params['polygon_threshold'] = args.polygon_threshold
803 return run_api_script('reverse', args.project_dir,
804 phpcgi_bin=args.phpcgi_path, params=params)
809 Execute API reverse query.
813 def add_args(parser):
814 group = parser.add_argument_group('Query arguments')
815 group.add_argument('--id', metavar='OSMID',
816 action='append', required=True, dest='ids',
817 help='OSM id to lookup in format <NRW><id> (may be repeated)')
819 _add_api_output_arguments(parser)
824 params = dict(osm_ids=','.join(args.ids))
826 for param, _ in EXTRADATA_PARAMS:
827 if getattr(args, param):
830 params['format'] = args.format
832 params['accept-language'] = args.lang
833 if args.polygon_output:
834 params['polygon_' + args.polygon_output] = '1'
835 if args.polygon_threshold:
836 params['polygon_threshold'] = args.polygon_threshold
838 return run_api_script('lookup', args.project_dir,
839 phpcgi_bin=args.phpcgi_path, params=params)
844 Execute API lookup query.
848 def add_args(parser):
849 group = parser.add_argument_group('Query arguments')
850 objs = group.add_mutually_exclusive_group(required=True)
851 objs.add_argument('--node', '-n', type=int,
852 help="Look up the OSM node with the given ID.")
853 objs.add_argument('--way', '-w', type=int,
854 help="Look up the OSM way with the given ID.")
855 objs.add_argument('--relation', '-r', type=int,
856 help="Look up the OSM relation with the given ID.")
857 objs.add_argument('--place_id', '-p', type=int,
858 help='Database internal identifier of the OSM object to look up.')
859 group.add_argument('--class', dest='object_class',
860 help="""Class type to disambiguated multiple entries
861 of the same object.""")
863 group = parser.add_argument_group('Output arguments')
864 for name, desc in DETAILS_SWITCHES:
865 group.add_argument('--' + name, action='store_true', help=desc)
866 group.add_argument('--lang', '--accept-language', metavar='LANGS',
867 help='Preferred language order for presenting search results')
872 params = dict(osmtype='N', osmid=args.node)
874 params = dict(osmtype='W', osmid=args.node)
876 params = dict(osmtype='R', osmid=args.node)
878 params = dict(place_id=args.place_id)
879 if args.object_class:
880 params['class'] = args.object_class
881 for name, _ in DETAILS_SWITCHES:
882 params[name] = '1' if getattr(args, name) else '0'
884 return run_api_script('details', args.project_dir,
885 phpcgi_bin=args.phpcgi_path, params=params)
890 Execute API status query.
894 def add_args(parser):
895 group = parser.add_argument_group('API parameters')
896 group.add_argument('--format', default='text', choices=['text', 'json'],
897 help='Format of result')
901 return run_api_script('status', args.project_dir,
902 phpcgi_bin=args.phpcgi_path,
903 params=dict(format=args.format))
906 def nominatim(**kwargs):
908 Command-line tools for importing, updating, administrating and
909 querying the Nominatim database.
911 parser = CommandlineParser('nominatim', nominatim.__doc__)
913 parser.add_subcommand('import', SetupAll)
914 parser.add_subcommand('freeze', SetupFreeze)
915 parser.add_subcommand('replication', UpdateReplication)
917 parser.add_subcommand('check-database', AdminCheckDatabase)
918 parser.add_subcommand('warm', AdminWarm)
920 parser.add_subcommand('special-phrases', SetupSpecialPhrases)
922 parser.add_subcommand('add-data', UpdateAddData)
923 parser.add_subcommand('index', UpdateIndex)
924 parser.add_subcommand('refresh', UpdateRefresh)
926 parser.add_subcommand('export', QueryExport)
927 parser.add_subcommand('serve', AdminServe)
929 if kwargs.get('phpcgi_path'):
930 parser.add_subcommand('search', APISearch)
931 parser.add_subcommand('reverse', APIReverse)
932 parser.add_subcommand('lookup', APILookup)
933 parser.add_subcommand('details', APIDetails)
934 parser.add_subcommand('status', APIStatus)
936 parser.parser.epilog = 'php-cgi not found. Query commands not available.'
938 return parser.run(**kwargs)