6 from copy import deepcopy
7 from datetime import timedelta
8 from typing import List, NamedTuple, Optional
13 from osgeo.ogr import Layer, Geometry, wkbPoint, Feature
14 from osgeo.osr import SpatialReference, CoordinateTransformation, OAMS_TRADITIONAL_GIS_ORDER
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
21 class DistInfo(NamedTuple):
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
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',
41 response = vao.trip(parameter).json()
42 trip = response.get('Trip', [])
44 leg_list = trip[0].get('LegList', {}).get('Leg', [])
47 duration = isodate.parse_duration(leg['duration'])
48 duration_minutes = int(round(duration / timedelta(minutes=1)))
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:
55 print('Unexpected result from VAO')
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)
63 spatial_reference_m = SpatialReference()
64 spatial_reference_m.ImportFromProj4(f'+proj=merc +lat_ts={(a.GetY() + b.GetY()) / 2}')
66 ll_to_m = CoordinateTransformation(spatial_reference_ll, spatial_reference_m)
69 a_m.Transform(ll_to_m)
72 b_m.Transform(ll_to_m)
74 return a_m.Distance(b_m)
77 def dist_info_to_dict(dist_info: DistInfo) -> dict:
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),
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)
91 sledrun_json_orig = sledrun_json.copy()
93 car_parking = sledrun_json.get('car_parking')
95 print(' (no parking)')
99 parking = car_parking[0].get('position', {}).get('position')
102 parking_lon = parking['longitude']
103 parking_lat = parking['latitude']
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:
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:
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)
125 spatial_reference_ll = SpatialReference()
126 spatial_reference_ll.ImportFromEPSG(4326)
127 spatial_reference_ll.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER)
129 spatial_reference_m = SpatialReference()
130 spatial_reference_m.ImportFromProj4(f'+proj=merc +lat_ts={parking_lat}')
132 ll_to_m = CoordinateTransformation(spatial_reference_ll, spatial_reference_m)
133 m_to_ll = CoordinateTransformation(spatial_reference_m, spatial_reference_ll)
135 parking_ll = Geometry(wkbPoint)
136 parking_ll.AddPoint(parking_lon, parking_lat)
138 parking_m = parking_ll.Clone()
139 parking_m.Transform(ll_to_m)
141 bound_m = parking_m.Buffer(max_dist_m)
142 bound_ll = bound_m.Clone()
143 bound_ll.Transform(m_to_ll)
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))
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):
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'])
166 car_distances_to_append = sorted(car_distances_to_append, key=lambda di: di['km'])[:max_number_distances]
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)
172 car_distance_list = sorted(car_distance_list, key=lambda di: di['km'])
173 sledrun_json['car_distances'] = car_distance_list
175 if sledrun_json == sledrun_json_orig:
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)
185 pageid=sledrun_json_page['pageid'],
186 text=sledrun_json_str,
187 summary=f'Entfernungen zu {title} aktualisiert (dank VAO).',
190 baserevid=sledrun_json_page['revisions'][0]['revid'],
196 def update_car_distances(ini_files: List[str]):
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')
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)
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']:
214 if page['title'] in ['Anzère']:
216 update_sledrun(vao, db_cities, site, page['title'])
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)
226 if __name__ == '__main__':