]> git.openstreetmap.org Git - nominatim.git/blob - src/nominatim_db/clicmd/refresh.py
Merge remote-tracking branch 'upstream/master'
[nominatim.git] / src / nominatim_db / clicmd / refresh.py
1 # SPDX-License-Identifier: GPL-3.0-or-later
2 #
3 # This file is part of Nominatim. (https://nominatim.org)
4 #
5 # Copyright (C) 2024 by the Nominatim developer community.
6 # For a full list of authors see the git log.
7 """
8 Implementation of 'refresh' subcommand.
9 """
10 from typing import Tuple, Optional
11 import argparse
12 import logging
13 from pathlib import Path
14 import asyncio
15
16 from ..config import Configuration
17 from ..db.connection import connect, table_exists
18 from ..tokenizer.base import AbstractTokenizer
19 from .args import NominatimArgs
20
21
22 LOG = logging.getLogger()
23
24
25 def _parse_osm_object(obj: str) -> Tuple[str, int]:
26     """ Parse the given argument into a tuple of OSM type and ID.
27         Raises an ArgumentError if the format is not recognized.
28     """
29     if len(obj) < 2 or obj[0].lower() not in 'nrw' or not obj[1:].isdigit():
30         raise argparse.ArgumentTypeError("Cannot parse OSM ID. Expect format: [N|W|R]<id>.")
31
32     return (obj[0].upper(), int(obj[1:]))
33
34
35 class UpdateRefresh:
36     """\
37     Recompute auxiliary data used by the indexing process.
38
39     This sub-commands updates various static data and functions in the database.
40     It usually needs to be run after changing various aspects of the
41     configuration. The configuration documentation will mention the exact
42     command to use in such case.
43
44     Warning: the 'update' command must not be run in parallel with other update
45              commands like 'replication' or 'add-data'.
46     """
47     def __init__(self) -> None:
48         self.tokenizer: Optional[AbstractTokenizer] = None
49
50     def add_args(self, parser: argparse.ArgumentParser) -> None:
51         group = parser.add_argument_group('Data arguments')
52         group.add_argument('--postcodes', action='store_true',
53                            help='Update postcode centroid table')
54         group.add_argument('--word-tokens', action='store_true',
55                            help='Clean up search terms')
56         group.add_argument('--word-counts', action='store_true',
57                            help='Compute frequency of full-word search terms')
58         group.add_argument('--address-levels', action='store_true',
59                            help='Reimport address level configuration')
60         group.add_argument('--functions', action='store_true',
61                            help='Update the PL/pgSQL functions in the database')
62         group.add_argument('--wiki-data', action='store_true',
63                            help='Update Wikipedia/data importance numbers')
64         group.add_argument('--secondary-importance', action='store_true',
65                            help='Update secondary importance raster data')
66         group.add_argument('--importance', action='store_true',
67                            help='Recompute place importances (expensive!)')
68         group.add_argument('--website', action='store_true',
69                            help='DEPRECATED. This function has no function anymore'
70                                 ' and will be removed in a future version.')
71         group.add_argument('--data-object', action='append',
72                            type=_parse_osm_object, metavar='OBJECT',
73                            help='Mark the given OSM object as requiring an update'
74                                 ' (format: [NWR]<id>)')
75         group.add_argument('--data-area', action='append',
76                            type=_parse_osm_object, metavar='OBJECT',
77                            help='Mark the area around the given OSM object as requiring an update'
78                                 ' (format: [NWR]<id>)')
79
80         group = parser.add_argument_group('Arguments for function refresh')
81         group.add_argument('--no-diff-updates', action='store_false', dest='diffs',
82                            help='Do not enable code for propagating updates')
83         group.add_argument('--enable-debug-statements', action='store_true',
84                            help='Enable debug warning statements in functions')
85
86     def run(self, args: NominatimArgs) -> int:
87         from ..tools import refresh, postcodes
88         from ..indexer.indexer import Indexer
89
90         need_function_refresh = args.functions
91
92         if args.postcodes:
93             if postcodes.can_compute(args.config.get_libpq_dsn()):
94                 LOG.warning("Update postcodes centroid")
95                 tokenizer = self._get_tokenizer(args.config)
96                 postcodes.update_postcodes(args.config.get_libpq_dsn(),
97                                            args.project_dir, tokenizer)
98                 indexer = Indexer(args.config.get_libpq_dsn(), tokenizer,
99                                   args.threads or 1)
100                 asyncio.run(indexer.index_postcodes())
101             else:
102                 LOG.error("The place table doesn't exist. "
103                           "Postcode updates on a frozen database is not possible.")
104
105         if args.word_tokens:
106             LOG.warning('Updating word tokens')
107             tokenizer = self._get_tokenizer(args.config)
108             tokenizer.update_word_tokens()
109
110         if args.word_counts:
111             LOG.warning('Recompute word statistics')
112             self._get_tokenizer(args.config).update_statistics(args.config,
113                                                                threads=args.threads or 1)
114
115         if args.address_levels:
116             LOG.warning('Updating address levels')
117             with connect(args.config.get_libpq_dsn()) as conn:
118                 refresh.load_address_levels_from_config(conn, args.config)
119
120         # Attention: must come BEFORE functions
121         if args.secondary_importance:
122             with connect(args.config.get_libpq_dsn()) as conn:
123                 # If the table did not exist before, then the importance code
124                 # needs to be enabled.
125                 if not table_exists(conn, 'secondary_importance'):
126                     args.functions = True
127
128             LOG.warning('Import secondary importance raster data from %s', args.project_dir)
129             if refresh.import_secondary_importance(args.config.get_libpq_dsn(),
130                                                    args.project_dir) > 0:
131                 LOG.fatal('FATAL: Cannot update secondary importance raster data')
132                 return 1
133             need_function_refresh = True
134
135         if args.wiki_data:
136             data_path = Path(args.config.WIKIPEDIA_DATA_PATH
137                              or args.project_dir)
138             LOG.warning('Import wikipedia article importance from %s', data_path)
139             if refresh.import_wikipedia_articles(args.config.get_libpq_dsn(),
140                                                  data_path) > 0:
141                 LOG.fatal('FATAL: Wikipedia importance file not found in %s', data_path)
142                 return 1
143             need_function_refresh = True
144
145         if need_function_refresh:
146             LOG.warning('Create functions')
147             with connect(args.config.get_libpq_dsn()) as conn:
148                 refresh.create_functions(conn, args.config,
149                                          args.diffs, args.enable_debug_statements)
150                 self._get_tokenizer(args.config).update_sql_functions(args.config)
151
152         # Attention: importance MUST come after wiki data import and after functions.
153         if args.importance:
154             LOG.warning('Update importance values for database')
155             with connect(args.config.get_libpq_dsn()) as conn:
156                 refresh.recompute_importance(conn)
157
158         if args.website:
159             LOG.error('WARNING: Website setup is no longer required. '
160                       'This function will be removed in future version of Nominatim.')
161
162         if args.data_object or args.data_area:
163             with connect(args.config.get_libpq_dsn()) as conn:
164                 for obj in args.data_object or []:
165                     refresh.invalidate_osm_object(*obj, conn, recursive=False)
166                 for obj in args.data_area or []:
167                     refresh.invalidate_osm_object(*obj, conn, recursive=True)
168                 conn.commit()
169
170         return 0
171
172     def _get_tokenizer(self, config: Configuration) -> AbstractTokenizer:
173         if self.tokenizer is None:
174             from ..tokenizer import factory as tokenizer_factory
175
176             self.tokenizer = tokenizer_factory.get_tokenizer_for_db(config)
177
178         return self.tokenizer