#!/usr/bin/python3 import argparse import json import sys from typing import List, Optional, Tuple from xml.etree.ElementTree import ElementTree, Element import geojson from geojson import Feature from shapely.geometry import shape from termcolor import cprint from wrpylib.cli_tools import unified_diff, input_yes_no_quit, Choice from wrpylib.json_tools import format_json from wrpylib.mwapi import WikiSite from wrpylib.wrgeojson import join_wrgeojson_ways, WrLineStringFeatureType, get_geometry_center, \ LanToToMetricTransformer, DEFAULT_MAX_DIST_ERROR_M from wrpylib.wrosm import find_sledrun_relations, convert_osm_to_geojson, DeRefError, tags def filter_way(feature: dict, way_type: str) -> bool: return feature['geometry']['type'] == 'LineString' and feature.get('properties', {}).get('type') == way_type def feature_distance(a: Feature, b: Feature) -> float: return shape(a['geometry']).hausdorff_distance(shape(b['geometry'])) def geojson_way_sort_key(feature: Feature, reference_feature: Feature) -> Tuple[str, float]: return feature.get('properties', {}).get('type', ''), feature_distance(feature, reference_feature) def update_sledrun_wrgeojson_to_highres(wrgeojson_title: str, osm_tree: ElementTree, osm_sledrun_relation: Element, max_dist_m: float, site: WikiSite): try: osm_wrgeojson = convert_osm_to_geojson(osm_tree, osm_sledrun_relation) except DeRefError: print('Error: Incomplete XML - please load larger region.') return join_wrgeojson_ways(osm_wrgeojson) wrgeojson_page = site.query_page(wrgeojson_title) wrgeojson_page_content = wrgeojson_page['revisions'][0]['slots']['main']['content'] wr_wrgeojson = geojson.loads(wrgeojson_page_content) center = get_geometry_center(wr_wrgeojson) transformer = LanToToMetricTransformer(center.coordinates[1]) for way_type in WrLineStringFeatureType: wr_ways = [f for f in wr_wrgeojson['features'] if filter_way(f, way_type.value)] osm_ways = [f for f in osm_wrgeojson['features'] if filter_way(f, way_type.value)] if len(wr_ways) != len(osm_ways): raise ValueError(f'{wrgeojson_title} does not have the same number of {way_type.value} features') for reference_feature in wr_wrgeojson['features']: reference_feature_type: str = reference_feature.get('properties', {}).get('type', '') if reference_feature_type not in [v.value for v in WrLineStringFeatureType]: continue osm_features: List[Feature] = [f for f in osm_wrgeojson['features'] if filter_way(f, reference_feature_type)] osm_features = sorted(osm_features, key=lambda f: geojson_way_sort_key(f, reference_feature)) osm_feature = osm_features[0] osm_feature_m = transformer.geojson_lon_lat_to_metric(osm_feature) reference_feature_m = transformer.geojson_lon_lat_to_metric(reference_feature) dist_m = feature_distance(osm_feature_m, reference_feature_m) if dist_m > max_dist_m: raise ValueError(f'Distance of ways "{reference_feature_type}" too big ({dist_m} m > {max_dist_m}).') reference_feature['geometry'] = osm_feature['geometry'] wr_wrgeojson_old = json.loads(wrgeojson_page_content) if wr_wrgeojson_old == wr_wrgeojson: return wr_wrgeojson['wr_properties']['simplified'] = False wr_wrgeojson_old_str = format_json(wr_wrgeojson_old) wr_wrgeojson_str = format_json(wr_wrgeojson) cprint(wrgeojson_title, 'green') unified_diff(wr_wrgeojson_old_str, wr_wrgeojson_str) choice = input_yes_no_quit('Do you accept the changes [yes, no, quit]? ', None) if choice == Choice.no: return elif choice == Choice.quit: sys.exit(0) site( 'edit', pageid=wrgeojson_page['pageid'], text=wr_wrgeojson_str, summary='Höhere Auflösung der Wege erstellt.', minor=1, bot=1, baserevid=wrgeojson_page['revisions'][0]['revid'], nocreate=1, token=site.token(), ) def update_wrgeojson_to_highres(wrgeojson_title: str, osm_file: str, osm_index: Optional[int], ini_files: List[str]): osm_tree = ElementTree() osm_tree.parse(osm_file) site = WikiSite(ini_files) # Search all sledrun relations osm_sledrun_list = list(find_sledrun_relations(osm_tree)) # Select one sledrun to work with if len(osm_sledrun_list) == 0: print('No sledruns in OSM file.') return elif len(osm_sledrun_list) > 1 and osm_index is None: print(f'{len(osm_sledrun_list)} sledruns in the OSM. Please specify --osm_index:') for i, osm_sledrun in enumerate(osm_sledrun_list): t = tags(osm_sledrun) print(f'{i}: {t.get("name", t)}') return osm_sledrun = osm_sledrun_list[0 if osm_index is None else osm_index] max_dist = DEFAULT_MAX_DIST_ERROR_M * 1.5 update_sledrun_wrgeojson_to_highres(wrgeojson_title, osm_tree, osm_sledrun, max_dist, site) def main(): parser = argparse.ArgumentParser( description='Updates sledrun wrgeojson maps with higher resolution ways from OSM files.') parser.add_argument('wrgeojson_title', help='Title of an existing wrgeojson page.') parser.add_argument('osm_file', help='OSM file containing high resolution ways.') parser.add_argument('--osm_index', type=int, help='Index of the sledrun relation within the OSM file.') parser.add_argument('ini_file', nargs='+', help='inifile.ini, see: https://www.winterrodeln.org/trac/wiki/ConfigIni') args = parser.parse_args() update_wrgeojson_to_highres(args.wrgeojson_title, args.osm_file, args.osm_index, args.ini_file) if __name__ == '__main__': main()