""" For a given German Zipcode, returns the corresponding WKT Polygon or Multipolygon. If pyperclip is installed, the WKT gets copied to the clipboard, e.g. for RegionChooser or download_files_from_LGL_BW.py. Also accepts multiple Zipcodes, or Zipcode prefix. usage: get_coordinates_by_zipcode.py [-h] [-p PRECISION] PLZ [PLZ ...] Get WKT geometry for desired PLZs positional arguments: PLZ desired PLZs options: -h, --help show this help message and exit -p PRECISION, --precision PRECISION precision of returned polygon [m] > python get_coordinates_by_zipcode.py 70174 > python get_coordinates_by_zipcode.py 70567 70569 > python get_coordinates_by_zipcode.py -p1000 70 """ # TODO: Write tests # TODO: Rename functions import argparse import json import re from pathlib import Path from shapely.geometry import shape from shapely.ops import unary_union INPUT_FOLDER = Path('plz') PLZ_FILENAME: str = 'plz-5stellig.geojson' PLZ_SHAPE_FILE = INPUT_FOLDER / PLZ_FILENAME PRECISION: float = 10 # [m] ONE_DEGREE: float = 40e6 / 360 # [m] PLZ_SHAPES: dict CACHED: bool = False def _download_plz_shapes_if_needed() -> None: if not PLZ_SHAPE_FILE.exists(): from tqdm import tqdm import requests print("Downloading %s..." % PLZ_FILENAME) URL = "https://downloads.suche-postleitzahl.org/v2/public/" + PLZ_FILENAME response = requests.get(URL, stream=True) INPUT_FOLDER.mkdir(exist_ok=True) with open(PLZ_SHAPE_FILE, "wb") as handle: for data in tqdm(response.iter_content(chunk_size=1024), unit='kB'): handle.write(data) print(' Done') def _get_plz_shapes() -> dict: global PLZ_SHAPES, CACHED if CACHED: return PLZ_SHAPES _download_plz_shapes_if_needed() try: print("Parsing %s..." % PLZ_FILENAME) with open(PLZ_SHAPE_FILE) as f: print(' Done') PLZ_SHAPES = json.load(f) CACHED = True return PLZ_SHAPES except json.decoder.JSONDecodeError: PLZ_SHAPE_FILE.unlink() raise AttributeError(f"{PLZ_FILENAME} seems to be damaged. Removing it. Please try again!") def get_coordinates_by_zipcode(plz_patterns: list[str], precision: float = PRECISION) -> str: plz_shapes = _get_plz_shapes() geometries = [] for plz_pattern in plz_patterns: found = False for plz_geojson in plz_shapes['features']: if re.match(plz_pattern, plz_geojson['properties']['plz']): found = True properties = plz_geojson['properties'] print('## %s' % properties['note']) print('Population : %d' % properties['einwohner']) print('Area : %.2f kmĀ²' % properties['qkm']) # NOTE : Geometry can be either a polygon, # a MultiPolygon : 98694 Ilmenau # or a polygon with holes : 31860 Emmerthal print('WKT Polygon : ') geometries.append(shape(plz_geojson['geometry'])) if not found: raise AttributeError(f"Sorry, no information could be found for PLZ={plz_pattern}") merged = unary_union(geometries) wkt_polygon = merged.simplify(precision / ONE_DEGREE).wkt print(wkt_polygon) try: import pyperclip pyperclip.copy(wkt_polygon) print("WKT Polygon copied to clipboard.") except ModuleNotFoundError: pass print() print("Done!") return wkt_polygon if __name__ == '__main__': parser = argparse.ArgumentParser(description='Get WKT geometry for desired PLZs') parser.add_argument('plzs', metavar='PLZ', type=str, nargs='+', help='desired PLZs') parser.add_argument('-p', '--precision', default=PRECISION, type=int, help='precision of returned polygon [m]') args = parser.parse_args() get_coordinates_by_zipcode(args.plzs, args.precision)