Added code, notebooks, prints, README, requirements.txt

pull/3/head
Marcelo Prates 2021-03-05 11:06:57 -03:00
rodzic 4cdf462c1c
commit 49fe89fae1
12 zmienionych plików z 394425 dodań i 0 usunięć

Wyświetl plik

@ -1,2 +1,32 @@
# prettymaps
A small set of Python functions to draw pretty maps from OpenStreetMap data. Based on osmnx, matplotlib and shapely libraries.
## Install dependencies
Install dependencies with
`$ pip install -r requirements.txt`
## Usage
On Python run:
```
from draw import plot
plot(f'Bom Fim, Porto Alegre', palette = ['red', 'blue'], layers = ['perimeter', 'landuse', 'water', 'streets'])
```
## "Circle" plots ([Jupyter Notebook](/notebooks/world-tour.ipynb)):
![](prints/Macau.svg)
![](prints/Palmanova.svg)
![](prints/Erbil.svg)
# Plotting districts ([Jupyter Notebook](/notebooks/porto-alegre.ipynb)):
![](prints/Centro%20Histórico%20-%20Porto%20Alegre.svg)
![](prints/Bom%20Fim%20-%20Porto%20Alegre.svg)
![](prints/Cidade%20Baixa%20-%20Porto%20Alegre.svg)
## More than one district at a time:
![](prints/CB-R-BF.svg)

182
code/draw.py 100644
Wyświetl plik

@ -0,0 +1,182 @@
# OpenStreetMap Networkx library to download data from OpenStretMap
import osmnx as ox
# Matplotlib-related stuff, for drawing
from matplotlib.path import Path
from matplotlib import pyplot as plt
import matplotlib.patches as patches
from matplotlib.patches import PathPatch
# CV2 & Scipy & Numpy & Pandas
import numpy as np
from numpy.random import choice
# Shapely
from shapely.geometry import *
from shapely.affinity import *
# Geopandas
from geopandas import GeoDataFrame
# etc
import pandas as pd
from functools import reduce
from tabulate import tabulate
from IPython.display import Markdown
from collections.abc import Iterable
# Fetch
from fetch import *
# Drawing functions
def show_palette(palette, description = ''):
'''
Helper to display palette in Markdown
'''
colorboxes = [
f'![](https://placehold.it/30x30/{c[1:]}/{c[1:]}?text=)'
for c in palette
]
display(Markdown((description)))
display(Markdown(tabulate(pd.DataFrame(colorboxes), showindex = False)))
def get_patch(shape, **kwargs):
'''
Convert shapely object to matplotlib patch
'''
if type(shape) == Path:
return patches.PathPatch(shape, **kwargs)
elif type(shape) == Polygon and shape.area > 0:
return patches.Polygon(list(zip(*shape.exterior.xy)), **kwargs)
else:
return None
def plot_shape(shape, ax, **kwargs):
'''
Plot shapely object
'''
if isinstance(shape, Iterable):
for shape_ in shape:
plot_shape(shape_, ax, **kwargs)
else:
ax.add_patch(get_patch(shape, **kwargs))
def plot_shapes(shapes, ax, palette = None, **kwargs):
'''
Plot collection of shapely objects (optionally, use a color palette)
'''
if not isinstance(shapes, Iterable):
shapes = [shapes]
for shape in shapes:
if palette is None:
plot_shape(shape, ax, **kwargs)
else:
plot_shape(shape, ax, fc = choice(palette), **kwargs)
def plot_streets(streets, ax, color = '#f5da9f', background_color = 'white', **kwargs):
'''
Plot shapely Polygon (or MultiPolygon) representing streets using matplotlib PathPatches
'''
for s in streets if isinstance(streets, Iterable) else [streets]:
if s is not None:
ax.add_patch(get_patch(pathify(s), facecolor = color, edgecolor = 'black', **kwargs))
def plot(
# Address
query,
# Figure parameters
figsize = (10, 10),
ax = None,
title = None,
# Whether to plot a circle centered around the address; circle params
circle = False,
radius = 1000,
streets_radius = 1000,
# Street params
dilate_streets = 5,
draw_streets = True,
# Color params
background_color = 'white',
background_alpha = 1.,
palette = None,
perimeter_lw = 1,
perimeter_ec = 'black',
water_ec = 'black',
land_ec = 'black',
buildings_ec = 'black',
# Which layers to plot
layers = ['perimeter', 'landuse', 'water', 'building', 'streets'],
# Layer ordering params
zorder_perimeter = None,
zorder_landuse = None,
zorder_water = None,
zorder_streets = None,
zorder_building = None,
# Whether to fetch data using OSM Id
by_osmid = False
):
#############
### Fetch ###
#############
# Geocode central point
if not by_osmid:
point = ox.geocode(query)
# Fetch perimeter
perimeter = get_perimeter(query, by_osmid = by_osmid) if not circle else None
# Fetch buildings, land, water, streets
layers_dict = {}
for layer in layers:
if layer == 'perimeter':
pass
elif layer == 'streets':
layers_dict[layer], _ = get_streets(
**({'point': point, 'radius': streets_radius} if circle else {'perimeter': perimeter}),
dilate = dilate_streets
)
else:
layers_dict[layer], perimeter_ = get_footprints(
**({'point': point, 'radius': radius} if circle else {'perimeter': perimeter}),
footprint = layer
)
# Project perimeter
if 'perimeter' in layers:
layers_dict['perimeter'] = perimeter_ if circle else union(ox.project_gdf(perimeter).geometry)
############
### Plot ###
############
if ax is None:
# if ax is none, create figure
fig, ax = plt.subplots(figsize = figsize)
# Ajust axis
ax.axis('off')
ax.axis('equal')
ax.autoscale()
# Setup parameters for drawing layers
layer_kwargs = {
'perimeter': {'lw': perimeter_lw, 'ec': perimeter_ec, 'fc': background_color, 'alpha': background_alpha, 'zorder': zorder_perimeter},
'landuse': {'ec': land_ec, 'fc': '#53bd53', 'zorder': zorder_landuse},
'water': {'ec': water_ec, 'fc': '#a1e3ff', 'zorder': zorder_water},
'streets': {'fc': '#f5da9f', 'zorder': zorder_streets},
'building': {'ec': buildings_ec, 'palette': palette, 'zorder': zorder_building},
}
# Draw layers
for layer in ['perimeter', 'landuse', 'water', 'streets', 'building']:
if layer in layers_dict:
plot_shapes(layers_dict[layer], ax, **layer_kwargs[layer])
# Return perimeter
return layers_dict['perimeter']

106
code/fetch.py 100644
Wyświetl plik

@ -0,0 +1,106 @@
# OpenStreetMap Networkx library to download data from OpenStretMap
import osmnx as ox
# CV2 & Scipy & Numpy & Pandas
import numpy as np
# Shapely
from shapely.geometry import *
from shapely.affinity import *
# Geopandas
from geopandas import GeoDataFrame
# Matplotlib
from matplotlib.path import Path
# etc
from collections.abc import Iterable
from functools import reduce
# Helper functions to fetch data from OSM
def ring_coding(ob):
codes = np.ones(len(ob.coords), dtype = Path.code_type) * Path.LINETO
codes[0] = Path.MOVETO
return codes
def pathify(polygon):
vertices = np.concatenate([np.asarray(polygon.exterior)] + [np.asarray(r) for r in polygon.interiors])
codes = np.concatenate([ring_coding(polygon.exterior)] + [ring_coding(r) for r in polygon.interiors])
return Path(vertices, codes)
def union(geometry):
geometry = np.concatenate([[x] if type(x) == Polygon else x for x in geometry if type(x) in [Polygon, MultiPolygon]])
geometry = reduce(lambda x, y: x.union(y), geometry[1:], geometry[0])
return geometry
def get_perimeter(query, by_osmid = False):
return ox.geocode_to_gdf(query, by_osmid = by_osmid)
def get_footprints(perimeter = None, point = None, radius = None, footprint = 'building'):
if perimeter is not None:
# Boundary defined by polygon (perimeter)
footprints = ox.geometries_from_polygon(union(perimeter.geometry), tags = {footprint: True} if type(footprint) == str else footprint)
perimeter = union(ox.project_gdf(perimeter).geometry)
elif (point is not None) and (radius is not None):
# Boundary defined by circle with radius 'radius' around point
footprints = ox.geometries_from_point(point, dist = radius, tags = {footprint: True} if type(footprint) == str else footprint)
perimeter = GeoDataFrame(geometry=[Point(point[::-1])], crs = footprints.crs)
perimeter = ox.project_gdf(perimeter).geometry[0].buffer(radius)
if len(footprints) > 0:
footprints = ox.project_gdf(footprints)
footprints = [
[x] if type(x) == Polygon else x
for x in footprints.geometry if type(x) in [Polygon, MultiPolygon]
]
footprints = list(np.concatenate(footprints)) if len(footprints) > 0 else []
footprints = [pathify(x) for x in footprints if x.within(perimeter)]
return footprints, perimeter
def get_streets(perimeter = None, point = None, radius = None, dilate = 6, custom_filter = None):
if perimeter is not None:
# Boundary defined by polygon (perimeter)
streets = ox.graph_from_polygon(union(perimeter.geometry), custom_filter = custom_filter)
streets = ox.project_graph(streets)
streets = ox.graph_to_gdfs(streets, nodes = False)
#streets = ox.project_gdf(streets)
streets = MultiLineString(list(streets.geometry)).buffer(dilate)
elif (point is not None) and (radius is not None):
# Boundary defined by polygon (perimeter)
streets = ox.graph_from_point(point, dist = radius, custom_filter = custom_filter)
crs = ox.graph_to_gdfs(streets, nodes = False).crs
streets = ox.project_graph(streets)
perimeter = GeoDataFrame(geometry=[Point(point[::-1])], crs = crs)
perimeter = ox.project_gdf(perimeter).geometry[0].buffer(radius)
streets = ox.graph_to_gdfs(streets, nodes = False)
streets = MultiLineString(list(
filter(
# Filter lines with at least 2 points
lambda line: len(line) >= 2,
# Iterate over lines in geometry
map(
# Filter points within perimeter
lambda line: list(filter(lambda xy: Point(xy).within(perimeter), zip(*line.xy))),
streets.geometry
)
)
)).buffer(dilate) # Dilate lines
if not isinstance(streets, Iterable):
streets = [streets]
streets = list(map(pathify, streets))
return streets, perimeter

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Plik diff jest za duży Load Diff

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 621 KiB

69734
prints/CB-R-BF.svg 100644

Plik diff jest za duży Load Diff

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 1.9 MiB

Plik diff jest za duży Load Diff

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 2.1 MiB

Plik diff jest za duży Load Diff

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 1021 KiB

74329
prints/Erbil.svg 100644

Plik diff jest za duży Load Diff

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 2.1 MiB

89121
prints/Macau.svg 100644

Plik diff jest za duży Load Diff

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 2.4 MiB

27294
prints/Palmanova.svg 100644

Plik diff jest za duży Load Diff

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 735 KiB