]> ToastFreeware Gitweb - philipp/winterrodeln/wrpylib.git/blob - scripts/update_car_distances.py
Reduce number of queries to get the car distance list.
[philipp/winterrodeln/wrpylib.git] / scripts / update_car_distances.py
1 #!/usr/bin/python
2 import argparse
3 import configparser
4 import re
5 import sys
6 from copy import deepcopy
7 from datetime import timedelta
8 from typing import List, NamedTuple, Optional
9
10 import isodate
11 import jsonschema
12 from osgeo import ogr
13 from osgeo.ogr import Layer, Geometry, wkbPoint, Feature
14 from osgeo.osr import SpatialReference, CoordinateTransformation, OAMS_TRADITIONAL_GIS_ORDER
15
16 from wrpylib.json_tools import order_json_keys, format_json
17 from wrpylib.mwapi import WikiSite, page_json
18 from wrpylib.vao import Vao
19
20
21 class DistInfo(NamedTuple):
22     city_name: str
23     geoname_id: int
24     duration_minutes: int
25     dist_m: int
26     co2_kg: float
27
28
29 def vao_car_distance(vao: Vao, parking_lon: float, parking_lat: float, city: Feature) -> Optional[DistInfo]:
30     geometry = city.GetGeometryRef()
31     point = geometry.GetPoint(0)
32     city_lon, city_lat, _ = point
33     parameter = {
34         'originCoordLat': city_lat,
35         'originCoordLong': city_lon,
36         'destCoordLat': parking_lat,
37         'destCoordLong': parking_lon,
38         'groupFilter': 'API_CAR',
39         'totalCar': '1|evnt=0,aevnt=0,tsta=0,htsta=0,getInitEndTimes=0',
40     }
41     response = vao.trip(parameter).json()
42     trip = response.get('Trip', [])
43     if trip:
44         leg_list = trip[0].get('LegList', {}).get('Leg', [])
45         if leg_list:
46             leg = leg_list[0]
47             duration = isodate.parse_duration(leg['duration'])
48             duration_minutes = int(round(duration / timedelta(minutes=1)))
49             dist_m = leg['dist']
50             co2_kg = trip[0]['Eco']['co2']
51             return DistInfo(city['name'], city['geonames_id'], duration_minutes, dist_m, co2_kg)
52     if response.get('errorCode') is not None:
53         print(response)
54         sys.exit(1)
55     print('Unexpected result from VAO')
56
57
58 def distance_meter(a: Geometry, b: Geometry) -> float:
59     spatial_reference_ll = SpatialReference()
60     spatial_reference_ll.ImportFromEPSG(4326)
61     spatial_reference_ll.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER)
62
63     spatial_reference_m = SpatialReference()
64     spatial_reference_m.ImportFromProj4(f'+proj=merc +lat_ts={(a.GetY() + b.GetY()) / 2}')
65
66     ll_to_m = CoordinateTransformation(spatial_reference_ll, spatial_reference_m)
67
68     a_m = a.Clone()
69     a_m.Transform(ll_to_m)
70
71     b_m = b.Clone()
72     b_m.Transform(ll_to_m)
73
74     return a_m.Distance(b_m)
75
76
77 def dist_info_to_dict(dist_info: DistInfo) -> dict:
78     return {
79         'km': round(dist_info.dist_m / 1000, 1),
80         'route': dist_info.city_name,
81         'minutes': dist_info.duration_minutes,
82         'geonames_id': dist_info.geoname_id,
83         'onward_co2_kg': round(dist_info.co2_kg, 1),
84     }
85
86
87 def update_sledrun(vao: Vao, db_cities: Layer, site: WikiSite, title: str):
88     sledrun_json_page = site.query_page(f'{title}/Rodelbahn.json')
89     sledrun_json = page_json(sledrun_json_page)
90
91     sledrun_json_orig = sledrun_json.copy()
92
93     car_parking = sledrun_json.get('car_parking')
94     if not car_parking:
95         print('  (no parking)')
96         print('')
97         return
98
99     parking = car_parking[0].get('position', {}).get('position')
100     if not parking:
101         return
102     parking_lon = parking['longitude']
103     parking_lat = parking['latitude']
104
105     car_distance_list = deepcopy(sledrun_json.get('car_distances', []))
106     if len([car_distance for car_distance in car_distance_list if car_distance.get('geonames_id') is not None]) > 0:
107         return
108
109     db_cities.SetSpatialFilter(None)
110     db_cities.SetAttributeFilter(None)
111     for car_distance in car_distance_list:
112         if car_distance.get('geonames_id') is None:
113             name = car_distance['route']
114             match = re.match(r'([-\w. ]+)\(?.*$', name)
115             if match is not None:
116                 name = match.group(1).strip()
117                 candidates = [city for city in db_cities if city['name'] == name]
118                 if len(candidates) == 1:
119                     city = candidates[0]
120                     dist_info = vao_car_distance(vao, parking_lon, parking_lat, city)
121                     if dist_info is not None:
122                         dist_info_dict = dist_info_to_dict(dist_info)
123                         car_distance.update(dist_info_dict)
124
125     spatial_reference_ll = SpatialReference()
126     spatial_reference_ll.ImportFromEPSG(4326)
127     spatial_reference_ll.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER)
128
129     spatial_reference_m = SpatialReference()
130     spatial_reference_m.ImportFromProj4(f'+proj=merc +lat_ts={parking_lat}')
131
132     ll_to_m = CoordinateTransformation(spatial_reference_ll, spatial_reference_m)
133     m_to_ll = CoordinateTransformation(spatial_reference_m, spatial_reference_ll)
134
135     parking_ll = Geometry(wkbPoint)
136     parking_ll.AddPoint(parking_lon, parking_lat)
137
138     parking_m = parking_ll.Clone()
139     parking_m.Transform(ll_to_m)
140     max_dist_m = 60000
141     bound_m = parking_m.Buffer(max_dist_m)
142     bound_ll = bound_m.Clone()
143     bound_ll.Transform(m_to_ll)
144
145     db_cities.SetSpatialFilter(bound_ll)
146     db_cities.SetAttributeFilter('level<=2')
147     close_cities = [city for city in db_cities]
148     close_cities = sorted(close_cities, key=lambda city: distance_meter(city.GetGeometryRef(), parking_ll))
149
150     max_number_distances = 3
151     car_distances_to_append: List[dict] = []
152     for city in close_cities:
153         if len(car_distances_to_append) >= max_number_distances:
154             if car_distances_to_append[2]['km'] * 1000 < distance_meter(city.GetGeometryRef(), parking_ll):
155                 break
156         print(city['name'])
157         car_distance = next((cd for cd in car_distance_list if cd.get('geonames_id') == city['geonames_id']), None)
158         if car_distance is None:
159             car_distance = vao_car_distance(vao, parking_lon, parking_lat, city)
160             if car_distance is not None:
161                 car_distance = dist_info_to_dict(car_distance)
162         if car_distance is not None:
163             car_distances_to_append.append(car_distance)
164         car_distances_to_append = sorted(car_distances_to_append, key=lambda di: di['km'])
165
166     car_distances_to_append = sorted(car_distances_to_append, key=lambda di: di['km'])[:max_number_distances]
167
168     for car_distance in car_distances_to_append:
169         if len([cd for cd in car_distance_list if cd.get('geonames_id') == car_distance['geonames_id']]) == 0:
170             car_distance_list.append(car_distance)
171
172     car_distance_list = sorted(car_distance_list, key=lambda di: di['km'])
173     sledrun_json['car_distances'] = car_distance_list
174
175     if sledrun_json == sledrun_json_orig:
176         return
177
178     jsonschema.validate(instance=sledrun_json, schema=site.sledrun_schema())
179     sledrun_json_ordered = order_json_keys(sledrun_json, site.sledrun_schema())
180     assert sledrun_json_ordered == sledrun_json
181     sledrun_json_str = format_json(sledrun_json_ordered)
182
183     site(
184         'edit',
185         pageid=sledrun_json_page['pageid'],
186         text=sledrun_json_str,
187         summary=f'Entfernungen zu {title} aktualisiert (dank VAO).',
188         # minor=1,
189         bot=1,
190         baserevid=sledrun_json_page['revisions'][0]['revid'],
191         nocreate=1,
192         token=site.token(),
193     )
194
195
196 def update_car_distances(ini_files: List[str]):
197     ogr.UseExceptions()
198
199     config = configparser.ConfigParser()
200     config.read(ini_files)
201     host = config.get('mysql', 'host')
202     dbname = config.get('mysql', 'dbname')
203     user = config.get('mysql', 'user_name')
204     passwd = config.get('mysql', 'user_pass')
205
206     cities_wr_source = ogr.Open(f'MySQL:{dbname},"host={host}","user={user}","password={passwd}","tables=wrcity"')
207     db_cities = cities_wr_source.GetLayerByIndex(0)
208
209     site = WikiSite(ini_files)
210     vao = Vao(config.get('vao', 'access_id'))
211     for result in site.query(list='categorymembers', cmtitle='Kategorie:Rodelbahn', cmlimit='max'):
212         for page in result['categorymembers']:
213             print(page['title'])
214             if page['title'] in ['Anzère']:
215                 continue
216             update_sledrun(vao, db_cities, site, page['title'])
217
218
219 def main():
220     parser = argparse.ArgumentParser(description='Update car distance information in sledrun JSON files.')
221     parser.add_argument('inifile', nargs='+', help='inifile.ini, see: https://www.winterrodeln.org/trac/wiki/ConfigIni')
222     args = parser.parse_args()
223     update_car_distances(args.inifile)
224
225
226 if __name__ == '__main__':
227     main()