]> ToastFreeware Gitweb - philipp/winterrodeln/wrpylib.git/blob - scripts/update_guetesiegel_from_official_list.py
VAO is missing important streets in Switzerland.
[philipp/winterrodeln/wrpylib.git] / scripts / update_guetesiegel_from_official_list.py
1 #!/usr/bin/python
2 """
3 Update sledrun JSON files based on OpenData published at
4 https://www.data.gv.at/katalog/dataset/land-tirol_rodelbahnenintirol
5
6 At the time of writing, the OpenData were be outdated - there is another official list:
7 https://www.tirol.gv.at/sport/richtlinien-und-initiativen/sport-guetesiegel/naturrodelbahnguetesiegel/rodelbahnen/
8
9
10 Get list of existing Gütesiegel entries:
11
12 SELECT
13     page_title,
14     json_extract(sledrun_json, '$.tiroler_naturrodelbahn_gütesiegel.name') AS name,
15     json_extract(sledrun_json, '$.tiroler_naturrodelbahn_gütesiegel.stätte_id') AS staette_id,
16     aswkt(
17         point(json_extract(sledrun_json, '$.position.longitude'),
18         json_extract(sledrun_json, '$.position.latitude'))) AS position,
19     json_extract(sledrun_json, '$.entry_under_construction') AS in_arbeit
20 FROM wrsledrunjsoncache JOIN page ON sledrun_page_id=page_id
21 WHERE json_exists(sledrun_json, '$.tiroler_naturrodelbahn_gütesiegel')
22 ORDER BY page_title;
23
24
25 Get existing Gütesiegel entry with matching stätte_id:
26
27 SELECT COUNT(*)
28 FROM wrsledrunjsoncache
29 WHERE json_extract(sledrun_json, '$.tiroler_naturrodelbahn_gütesiegel.stätte_id') = 3344;
30
31
32 Get list of sledruns closer to a given point (longitude, latitude) - distance is in meter:
33
34 SELECT page_title, name, aswkt(position) as position, in_arbeit, distance
35 FROM (
36     SELECT *, st_distance_sphere(point(11.4, 47.3), position) as distance
37     FROM (
38         SELECT
39             page_title,
40             json_extract(sledrun_json, '$.tiroler_naturrodelbahn_gütesiegel.name') AS name,
41             point(json_extract(sledrun_json, '$.position.longitude'),
42                 json_extract(sledrun_json, '$.position.latitude')) AS position,
43             json_extract(sledrun_json, '$.entry_under_construction') AS in_arbeit
44         FROM wrsledrunjsoncache JOIN page ON sledrun_page_id=page_id
45         WHERE json_exists(sledrun_json, '$.tiroler_naturrodelbahn_gütesiegel')
46     ) AS g
47 ) AS d
48 WHERE distance < 20000
49 ORDER BY distance;
50
51 """
52 import argparse
53 import configparser
54 import json
55 from typing import List, Union
56
57 import fiona
58 import requests
59 import sqlalchemy
60 from fiona.transform import transform_geom
61 from requests import Session
62 from shapely.geometry import shape
63 from sqlalchemy import create_engine, text
64
65 from wrpylib.json_tools import format_json
66
67
68 def update_sledrun(connection: sqlalchemy.engine.Connection, api_url: str, session: Session,
69                    csrf_token: str, shp_crs: Union[dict, str], lon_lat_crs: Union[dict, str], feature: dict):
70     # Is a sledrun already existing with the given stätte_id?
71     properties = feature['properties']
72
73     sledrun_type = properties['ATTR_TYP']
74     assert sledrun_type in ['Naturrodelbahn', 'Sommerrodelbahn']
75     if sledrun_type != 'Naturrodelbahn':
76         return
77
78     count, = connection.execute(text("""
79         SELECT COUNT(*)
80         FROM wrsledrunjsoncache
81         WHERE json_extract(sledrun_json, '$.tiroler_naturrodelbahn_gütesiegel.stätte_id') = :staette_id;"""),
82         [{'staette_id': properties['STAETTE_ID']}]).fetchone()
83
84     if count == 0:
85         # Is a sledrun already existing with the given name?
86         rows = list(connection.execute(text("""
87             SELECT sledrun_page_id, sledrun_json_page_id, sledrun_json_rev_id, sledrun_json
88             FROM wrsledrunjsoncache
89             WHERE json_extract(sledrun_json, '$.tiroler_naturrodelbahn_gütesiegel.name') = :name;"""),
90                                     [{'name': properties['STAETTE_NA']}]))
91         if len(rows) == 0:
92             print(f"{properties['STAETTE_NA']} ({properties['STAETTE_ID']}) not found")
93
94             # Is there a sledrun that's close?
95             line_string_lon_lat = transform_geom(shp_crs, lon_lat_crs, feature['geometry'])
96             point = shape(line_string_lon_lat).representative_point()
97             lon, lat = point.x, point.y
98             candidates = connection.execute(text("""
99             SELECT page_title, name, aswkt(position) as position, in_arbeit, distance
100             FROM (
101                 SELECT *, st_distance_sphere(point(:lon, :lat), position) as distance
102                 FROM (
103                     SELECT
104                         page_title,
105                         json_extract(sledrun_json, '$.tiroler_naturrodelbahn_gütesiegel.name') AS name,
106                         point(json_extract(sledrun_json, '$.position.longitude'),
107                             json_extract(sledrun_json, '$.position.latitude')) AS position,
108                         json_extract(sledrun_json, '$.entry_under_construction') AS in_arbeit
109                     FROM wrsledrunjsoncache JOIN page ON sledrun_page_id=page_id
110                 ) AS g
111             ) AS d
112             WHERE distance < 5000
113             ORDER BY distance;
114             """), [{'lon': lon, 'lat': lat}])
115             candidates = list(candidates)
116             if len(candidates) == 0:
117                 print('  No candidates.')
118             print('  Candidates:')
119             for candidate in candidates:
120                 print(f'    {candidate}')
121         elif len(rows) == 1:
122             print(f"{properties['STAETTE_NA']} -> {properties['STAETTE_ID']} (match by name)")
123
124             # add 'stätte_id' to JSON
125             row = rows[0]
126             sledrun_json = json.loads(row.sledrun_json)
127             gs = sledrun_json['tiroler_naturrodelbahn_gütesiegel']
128             if gs.get('stätte_id') is not None:
129                 print(f"{properties['STAETTE_NA']} has a different ID")
130                 return
131             gs['stätte_id'] = properties['STAETTE_ID']
132             new_sledrun_json = format_json(sledrun_json)
133             post_data = {
134                 'action': 'edit',
135                 'pageid': row.sledrun_json_page_id,
136                 'text': new_sledrun_json,
137                 'summary': '"Stätte-ID" von Tiroler Naturrodelbahn-Gütesiegel eingefügt.',
138                 'minor': 1,
139                 'bot': 1,
140                 'baserevid': row.sledrun_json_rev_id,
141                 'nocreate': 1,
142                 'contentformat': 'application/json',
143                 'contentmodel': 'json',
144                 'token': csrf_token,
145                 'format': 'json',
146             }
147             response = session.post(url=api_url, data=post_data)
148             response = response.json()['edit']
149             if response['result'] != 'Success':
150                 raise RuntimeError(response['reason'])
151             assert row.sledrun_json_page_id == response['pageid']
152             if 'nochange' in response:
153                 print("Change already done previously - table wrsledrunjsoncache doesn't seem to be up-to-date.")
154                 return
155             assert row.sledrun_json_rev_id == response['oldrevid']
156             print(f"Successfully add stätte_id {properties['STAETTE_ID']} to {properties['STAETTE_NA']}")
157
158             # update wrsledrunjsoncache (not strictly necessary but why not as we have the data)
159             connection.execute(text("""
160             UPDATE wrsledrunjsoncache
161             SET
162                 sledrun_json=:sledrun_json,
163                 sledrun_json_rev_id=:sledrun_json_rev_id
164             WHERE
165                 sledrun_page_id=:sledrun_page_id AND sledrun_json_page_id=:sledrun_json_page_id;
166             """), [{
167                 'sledrun_json': new_sledrun_json,
168                 'sledrun_json_rev_id': response['newrevid'],
169                 'sledrun_page_id': row.sledrun_page_id,
170                 'sledrun_json_page_id': row.sledrun_json_page_id,
171             }])
172
173         else:
174             print(f'{len(rows)} rows match the name {properties["STAETTE_NA"]}.')
175     else:
176         print(f"{properties['STAETTE_NA']} -> {properties['STAETTE_ID']} (match by stätte_id)")
177
178
179 def update_gütesiegel(inifile: List[str], shapefile: str):
180     config = configparser.ConfigParser()
181     config.read(inifile)
182
183     api_url = config.get('robot', 'wikiurl')
184     api_user = config.get('robot', 'botpassword_bot')
185     api_password = config.get('robot', 'botpassword_password')
186
187     session = requests.Session()
188
189     # get login_token
190     get_data = {
191         'action': 'query',
192         'meta': 'tokens',
193         'type': 'login',
194         'format': 'json',
195     }
196     response = session.get(url=api_url, params=get_data).json()
197     login_token = response['query']['tokens']['logintoken']
198
199     # login
200     post_data = {
201         'action': 'login',
202         'lgname': api_user,
203         'lgpassword': api_password,
204         'lgtoken': login_token,
205         'format': 'json'
206     }
207     response = session.post(url=api_url, data=post_data).json()['login']
208     if response['result'] != 'Success':
209         raise RuntimeError(response['reason'])
210
211     # get csrf_token
212     get_data = {
213         'action': 'query',
214         'meta': 'tokens',
215         'type': 'csrf',
216         'format': 'json',
217     }
218     response = session.get(url=api_url, params=get_data).json()
219     csrf_token = response['query']['tokens']['csrftoken']
220
221     host = config.get('mysql', 'host')
222     dbname = config.get('mysql', 'dbname')
223     user = config.get('mysql', 'user_name')
224     passwd = config.get('mysql', 'user_pass')
225
226     engine = create_engine(f'mysql://{user}@{host}:3306/{dbname}?passwd={passwd}&charset=utf8mb4')
227     connection = engine.connect()
228     with fiona.open(shapefile) as shp:
229         for feature in shp:
230             update_sledrun(connection, api_url, session, csrf_token, shp.crs, 'EPSG:4326', feature)
231
232
233 def main():
234     parser = argparse.ArgumentParser(description='Update Tiroler Gütesiegel in sledrun JSON '
235                                                  'based on official information.')
236     parser.add_argument('inifile', nargs='+', help='inifile.ini, see: https://www.winterrodeln.org/trac/wiki/ConfigIni')
237     parser.add_argument('shapefile', help='Shape file as provided by '
238                                           'https://www.data.gv.at/katalog/dataset/land-tirol_rodelbahnenintirol')
239     args = parser.parse_args()
240     update_gütesiegel(args.inifile, args.shapefile)
241
242
243 if __name__ == '__main__':
244     main()