|  | import argparse | 
        
          |  | import requests | 
        
          |  | import json | 
        
          |  | import geopandas as gpd | 
        
          |  | import pandas as pd | 
        
          |  | from shapely import affinity | 
        
          |  | from shapely.geometry import Polygon, Point, MultiPolygon | 
        
          |  | from matplotlib import pyplot as plt | 
        
          |  | import numpy as np | 
        
          |  | from osmtogeojson import osmtogeojson | 
        
          |  |  | 
        
          |  |  | 
        
          |  | #0) Get user input | 
        
          |  | cities = ["Zurich", "New_York_City","New_Delhi", "Berlin", "Barcelona", "Tokyo"] | 
        
          |  |  | 
        
          |  | citiesBbox_wgs = { | 
        
          |  | "Zurich": "(47.362414,8.513328,47.385392,8.559779)", #zurich | 
        
          |  | "New_York_City": "(40.713196, -74.017528, 40.739165, -73.967435)",#nyc | 
        
          |  | "New_Delhi": "(28.599522,77.194575,28.62432, 77.240522)", #new delhi | 
        
          |  | "Berlin":"(52.503223,13.350635,52.547340,13.447870)", #berlin | 
        
          |  | "Barcelona": "(41.375642,2.129504,41.401074,2.180285)", #barcelona | 
        
          |  | "Tokyo": "(35.640169,139.712369,35.704226,139.801369)" #tokyo | 
        
          |  | } | 
        
          |  |  | 
        
          |  | parser = argparse.ArgumentParser() | 
        
          |  | parser.add_argument("-c","--city", required=True, help="Select a city to organize", choices=cities) | 
        
          |  | city = parser.parse_args() | 
        
          |  | userinput = vars(parser.parse_args())["city"] | 
        
          |  |  | 
        
          |  | #1) Get data from OSM | 
        
          |  | def getData(citycode): | 
        
          |  | overpass_query = """ | 
        
          |  | [out:json][timeout:25]; | 
        
          |  | (way["building"]"""+ citycode +"""; | 
        
          |  | relation["building"]"""+ citycode +"""; | 
        
          |  | ); | 
        
          |  | out body; | 
        
          |  | >; | 
        
          |  | out skel qt; | 
        
          |  | """ | 
        
          |  | overpass_url = "http://overpass-api.de/api/interpreter?" | 
        
          |  | response = requests.get(overpass_url, params={'data': overpass_query}) | 
        
          |  | print(response) | 
        
          |  | response.raise_for_status() | 
        
          |  | return response.json() | 
        
          |  |  | 
        
          |  |  | 
        
          |  | def getProcessedData(citycode): | 
        
          |  | try: | 
        
          |  | r = getData(citycode) | 
        
          |  | return r | 
        
          |  | except requests.exceptions.ConnectionError: | 
        
          |  | print("Check your internet connection") | 
        
          |  | except requests.exceptions.HTTPError: | 
        
          |  | print("Sorry, there was a HTTPError in the HTTP request returned") | 
        
          |  |  | 
        
          |  |  | 
        
          |  | #2) Sort and prepare geodataframe by adding necessary values for the geometric translation | 
        
          |  | def prepareDf(data, sortFeature): | 
        
          |  | city = [] | 
        
          |  | for i in data["features"]: | 
        
          |  | if "coordinates" in i["geometry"] and i["geometry"]["type"] == "Polygon": | 
        
          |  | city.append(MultiPolygon([Polygon(poly) for poly in i["geometry"]["coordinates"]])) | 
        
          |  |  | 
        
          |  | city = gpd.GeoSeries(city) | 
        
          |  | city.crs = {'init': 'epsg:4326', 'no_defs': True} | 
        
          |  | city_proj = city.to_crs({'init': 'epsg:3857'}) | 
        
          |  |  | 
        
          |  | df = gpd.GeoDataFrame(pd.concat([city_proj.area, city_proj.length], axis=1), geometry = city_proj) | 
        
          |  | df.rename(columns = {0: "area", 1: "length"}, inplace=True) | 
        
          |  | df = df[df[sortFeature] != 0.0].sort_values(sortFeature).reset_index(drop=True) | 
        
          |  | df["lengthX"] = (df.bounds["maxx"] - df.bounds["minx"]).abs() | 
        
          |  | df["lengthY"] = (df.bounds["maxy"] - df.bounds["miny"]).abs() | 
        
          |  | df["cumulative_X"] = df["lengthX"].cumsum() #cumulative length | 
        
          |  | l_guess = max(df["cumulative_X"])*0.01 | 
        
          |  | return df, l_guess | 
        
          |  |  | 
        
          |  | #3) find optimized total width and height (ideal width / height ratio around 0.7) | 
        
          |  | def findWidth(df, l_guess, counter, max_iter): | 
        
          |  | tolerance = 0.01 #tolerated offset to expected ratio | 
        
          |  | r_expected = 0.7 #expected ratio | 
        
          |  | h = 0 | 
        
          |  | counter += 1 | 
        
          |  | df = df.copy() | 
        
          |  | df["row"] = [np.floor(i/l_guess) for i in df["cumulative_X"]] | 
        
          |  |  | 
        
          |  | for k in range(int(max(df["row"])+1)): | 
        
          |  | vals = [j for j,i in enumerate(df["row"]) if i == k] | 
        
          |  | h += max(df['lengthY'][vals])*1.1 | 
        
          |  | df.loc[vals, "new_X"] = df['lengthX'][vals].cumsum().shift(1, fill_value=0) | 
        
          |  | df.loc[vals, "new_Y"] = h | 
        
          |  |  | 
        
          |  | r = l_guess/h #calculated ratio | 
        
          |  |  | 
        
          |  | if abs(r_expected - r) <= tolerance or counter == max_iter: | 
        
          |  | print("optimal length is:", l_guess, "height is:", h, "ratio:", r) | 
        
          |  | return df | 
        
          |  | elif r < r_expected: #l_guess too small | 
        
          |  | l_guess += l_guess * 0.15 | 
        
          |  | return findWidth(df,l_guess,counter,max_iter) | 
        
          |  | else: #l_guess too big | 
        
          |  | l_guess -= l_guess * 0.15 | 
        
          |  | return findWidth(df,l_guess,counter, max_iter) | 
        
          |  |  | 
        
          |  | #4) Create translation vector and shift geometries | 
        
          |  | def translate(df): | 
        
          |  | df["translateY"] = -(df.bounds["miny"]) + df["new_Y"] | 
        
          |  | df["translateX"] = -df.bounds["minx"] + df["new_X"] | 
        
          |  | df["geometry"] = df.apply(lambda x: affinity.translate(x.geometry, xoff = x.translateX, yoff=x.translateY), axis=1) | 
        
          |  | return df | 
        
          |  |  | 
        
          |  | #4) Plot reorganized map and save as image | 
        
          |  | def draw_city(gdfFinal): | 
        
          |  | f, ax = plt.subplots(1, figsize = (10,15)) | 
        
          |  | ax = gdfFinal.plot(ax = ax, color='gray', linewidth =0.2) | 
        
          |  | lims = plt.axis("equal") | 
        
          |  | plt.axis('off') | 
        
          |  | plt.savefig(userinput + ".png") | 
        
          |  | plt.show() | 
        
          |  |  | 
        
          |  | def main(): | 
        
          |  | data = osmtogeojson.process_osm_json(getProcessedData(citiesBbox_wgs[userinput])) | 
        
          |  | data_prep = prepareDf(data, "length") | 
        
          |  | data_trans = findWidth(data_prep[0], data_prep[1], 0, 10) | 
        
          |  | draw_city(translate(data_trans)) | 
        
          |  |  | 
        
          |  | main() |