]> ToastFreeware Gitweb - philipp/winterrodeln/wrpylib.git/blob - scripts/update_wrgeojson_to_highres.py
VAO is missing important streets in Switzerland.
[philipp/winterrodeln/wrpylib.git] / scripts / update_wrgeojson_to_highres.py
1 #!/usr/bin/python3
2 import argparse
3 import json
4 import sys
5 from typing import List, Optional, Tuple
6 from xml.etree.ElementTree import ElementTree, Element
7
8 import geojson
9 from geojson import Feature
10 from shapely.geometry import shape
11 from termcolor import cprint
12
13 from wrpylib.cli_tools import unified_diff, input_yes_no_quit, Choice
14 from wrpylib.json_tools import format_json
15 from wrpylib.mwapi import WikiSite
16 from wrpylib.wrgeojson import join_wrgeojson_ways, WrLineStringFeatureType, get_geometry_center, \
17     LanToToMetricTransformer, DEFAULT_MAX_DIST_ERROR_M
18 from wrpylib.wrosm import find_sledrun_relations, convert_osm_to_geojson, DeRefError, tags
19
20
21 def filter_way(feature: dict, way_type: str) -> bool:
22     return feature['geometry']['type'] == 'LineString' and feature.get('properties', {}).get('type') == way_type
23
24
25 def feature_distance(a: Feature, b: Feature) -> float:
26     return shape(a['geometry']).hausdorff_distance(shape(b['geometry']))
27
28
29 def geojson_way_sort_key(feature: Feature, reference_feature: Feature) -> Tuple[str, float]:
30     return feature.get('properties', {}).get('type', ''), feature_distance(feature, reference_feature)
31
32
33 def update_sledrun_wrgeojson_to_highres(wrgeojson_title: str, osm_tree: ElementTree, osm_sledrun_relation: Element,
34                                         max_dist_m: float, site: WikiSite):
35     try:
36         osm_wrgeojson = convert_osm_to_geojson(osm_tree, osm_sledrun_relation)
37     except DeRefError:
38         print('Error: Incomplete XML - please load larger region.')
39         return
40     join_wrgeojson_ways(osm_wrgeojson)
41
42     wrgeojson_page = site.query_page(wrgeojson_title)
43     wrgeojson_page_content = wrgeojson_page['revisions'][0]['slots']['main']['content']
44     wr_wrgeojson = geojson.loads(wrgeojson_page_content)
45
46     center = get_geometry_center(wr_wrgeojson)
47     transformer = LanToToMetricTransformer(center.coordinates[1])
48
49     for way_type in WrLineStringFeatureType:
50         wr_ways = [f for f in wr_wrgeojson['features'] if filter_way(f, way_type.value)]
51         osm_ways = [f for f in osm_wrgeojson['features'] if filter_way(f, way_type.value)]
52         if len(wr_ways) != len(osm_ways):
53             raise ValueError(f'{wrgeojson_title} does not have the same number of {way_type.value} features')
54
55     for reference_feature in wr_wrgeojson['features']:
56         reference_feature_type: str = reference_feature.get('properties', {}).get('type', '')
57         if reference_feature_type not in [v.value for v in WrLineStringFeatureType]:
58             continue
59
60         osm_features: List[Feature] = [f for f in osm_wrgeojson['features'] if filter_way(f, reference_feature_type)]
61         osm_features = sorted(osm_features, key=lambda f: geojson_way_sort_key(f, reference_feature))
62         osm_feature = osm_features[0]
63         osm_feature_m = transformer.geojson_lon_lat_to_metric(osm_feature)
64         reference_feature_m = transformer.geojson_lon_lat_to_metric(reference_feature)
65
66         dist_m = feature_distance(osm_feature_m, reference_feature_m)
67         if dist_m > max_dist_m:
68             raise ValueError(f'Distance of ways "{reference_feature_type}" too big ({dist_m} m > {max_dist_m}).')
69
70         reference_feature['geometry'] = osm_feature['geometry']
71
72     wr_wrgeojson_old = json.loads(wrgeojson_page_content)
73     if wr_wrgeojson_old == wr_wrgeojson:
74         return
75
76     wr_wrgeojson['wr_properties']['simplified'] = False
77
78     wr_wrgeojson_old_str = format_json(wr_wrgeojson_old)
79     wr_wrgeojson_str = format_json(wr_wrgeojson)
80
81     cprint(wrgeojson_title, 'green')
82     unified_diff(wr_wrgeojson_old_str, wr_wrgeojson_str)
83     choice = input_yes_no_quit('Do you accept the changes [yes, no, quit]? ', None)
84     if choice == Choice.no:
85         return
86     elif choice == Choice.quit:
87         sys.exit(0)
88
89     site(
90         'edit',
91         pageid=wrgeojson_page['pageid'],
92         text=wr_wrgeojson_str,
93         summary='Höhere Auflösung der Wege erstellt.',
94         minor=1,
95         bot=1,
96         baserevid=wrgeojson_page['revisions'][0]['revid'],
97         nocreate=1,
98         token=site.token(),
99     )
100
101
102 def update_wrgeojson_to_highres(wrgeojson_title: str, osm_file: str, osm_index: Optional[int], ini_files: List[str]):
103     osm_tree = ElementTree()
104     osm_tree.parse(osm_file)
105
106     site = WikiSite(ini_files)
107
108     # Search all sledrun relations
109     osm_sledrun_list = list(find_sledrun_relations(osm_tree))
110
111     # Select one sledrun to work with
112     if len(osm_sledrun_list) == 0:
113         print('No sledruns in OSM file.')
114         return
115     elif len(osm_sledrun_list) > 1 and osm_index is None:
116         print(f'{len(osm_sledrun_list)} sledruns in the OSM. Please specify --osm_index:')
117         for i, osm_sledrun in enumerate(osm_sledrun_list):
118             t = tags(osm_sledrun)
119             print(f'{i}: {t.get("name", t)}')
120         return
121     osm_sledrun = osm_sledrun_list[0 if osm_index is None else osm_index]
122
123     max_dist = DEFAULT_MAX_DIST_ERROR_M * 1.5
124     update_sledrun_wrgeojson_to_highres(wrgeojson_title, osm_tree, osm_sledrun, max_dist, site)
125
126
127 def main():
128     parser = argparse.ArgumentParser(
129         description='Updates sledrun wrgeojson maps with higher resolution ways from OSM files.')
130     parser.add_argument('wrgeojson_title', help='Title of an existing wrgeojson page.')
131     parser.add_argument('osm_file', help='OSM file containing high resolution ways.')
132     parser.add_argument('--osm_index', type=int, help='Index of the sledrun relation within the OSM file.')
133     parser.add_argument('ini_file', nargs='+',
134                         help='inifile.ini, see: https://www.winterrodeln.org/trac/wiki/ConfigIni')
135     args = parser.parse_args()
136     update_wrgeojson_to_highres(args.wrgeojson_title, args.osm_file, args.osm_index, args.ini_file)
137
138
139 if __name__ == '__main__':
140     main()