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