Skip to content

Instantly share code, notes, and snippets.

@melanieimfeld
Last active April 27, 2022 23:15
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save melanieimfeld/676f958f279d8551c4be32bb9ee5f5d7 to your computer and use it in GitHub Desktop.
Save melanieimfeld/676f958f279d8551c4be32bb9ee5f5d7 to your computer and use it in GitHub Desktop.
Ordering Cities using Python
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()

This python commandline script grabs all buildings for a city from OpenStreepMap depending on the users choice and organizes the chosen city by rearranging the buildings by circumference length.

High level description of the operations:

  1. Get user input via Argparse
  2. Based on user input, get Data from OpenStreetmap
  3. Sort and prepare geodataframe by adding necessary values for the geometric translation
  4. find optimized total width and height (ideal width / height ratio around 0.7)
  5. Create translation vector and shift geometries
  6. Plot reorganized map and save as image
descartes==1.1.0
entrypoints==0.3
Fiona==1.8.4
GDAL==2.3.3
geojson==2.5.0
geopandas==0.6.1
matplotlib==3.1.3
numpy==1.18.1
osmtogeojson==0.0.2
pandas==1.0.3
pyproj==1.9.6
requests==2.23.0
Shapely==1.6.4.post1
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment