diff --git a/code/draw.py b/code/draw.py deleted file mode 100644 index 38043e6..0000000 --- a/code/draw.py +++ /dev/null @@ -1,185 +0,0 @@ -# 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, display -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, - by_coordinates = False, - ): - - ############# - ### Fetch ### - ############# - - # Geocode central point - if by_coordinates: - point = (float(query.split(",")[0].strip()), float(query.split(",")[1].strip())) - elif 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'] diff --git a/code/fetch.py b/code/fetch.py deleted file mode 100644 index 3e029fd..0000000 --- a/code/fetch.py +++ /dev/null @@ -1,106 +0,0 @@ -# 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 diff --git a/prettymaps/__init__.py b/prettymaps/__init__.py new file mode 100644 index 0000000..41e69ee --- /dev/null +++ b/prettymaps/__init__.py @@ -0,0 +1,3 @@ +# Fetch & Draw +from .draw import * +from .fetch import * \ No newline at end of file diff --git a/prettymaps/curved_text.py b/prettymaps/curved_text.py new file mode 100644 index 0000000..cb09e9f --- /dev/null +++ b/prettymaps/curved_text.py @@ -0,0 +1,164 @@ +from matplotlib import pyplot as plt +from matplotlib import patches +from matplotlib import text as mtext +import numpy as np +import math + +class CurvedText(mtext.Text): + """ + A text object that follows an arbitrary curve. + """ + def __init__(self, x, y, text, axes, **kwargs): + super(CurvedText, self).__init__(x[0],y[0],' ', **kwargs) + + axes.add_artist(self) + + ##saving the curve: + self.__x = x + self.__y = y + self.__zorder = self.get_zorder() + + ##creating the text objects + self.__Characters = [] + for c in text: + if c == ' ': + ##make this an invisible 'a': + t = mtext.Text(0,0,'a') + t.set_alpha(0.0) + else: + t = mtext.Text(0,0,c, **kwargs) + + #resetting unnecessary arguments + t.set_ha('center') + t.set_rotation(0) + t.set_zorder(self.__zorder +1) + + self.__Characters.append((c,t)) + axes.add_artist(t) + + + ##overloading some member functions, to assure correct functionality + ##on update + def set_zorder(self, zorder): + super(CurvedText, self).set_zorder(zorder) + self.__zorder = self.get_zorder() + for c,t in self.__Characters: + t.set_zorder(self.__zorder+1) + + def draw(self, renderer, *args, **kwargs): + """ + Overload of the Text.draw() function. Do not do + do any drawing, but update the positions and rotation + angles of self.__Characters. + """ + self.update_positions(renderer) + + def update_positions(self,renderer): + """ + Update positions and rotations of the individual text elements. + """ + + #preparations + + ##determining the aspect ratio: + ##from https://stackoverflow.com/a/42014041/2454357 + + ##data limits + xlim = self.axes.get_xlim() + ylim = self.axes.get_ylim() + ## Axis size on figure + figW, figH = self.axes.get_figure().get_size_inches() + ## Ratio of display units + _, _, w, h = self.axes.get_position().bounds + ##final aspect ratio + aspect = ((figW * w)/(figH * h))*(ylim[1]-ylim[0])/(xlim[1]-xlim[0]) + + #points of the curve in figure coordinates: + x_fig,y_fig = ( + np.array(l) for l in zip(*self.axes.transData.transform([ + (i,j) for i,j in zip(self.__x,self.__y) + ])) + ) + + #point distances in figure coordinates + x_fig_dist = (x_fig[1:]-x_fig[:-1]) + y_fig_dist = (y_fig[1:]-y_fig[:-1]) + r_fig_dist = np.sqrt(x_fig_dist**2+y_fig_dist**2) + + #arc length in figure coordinates + l_fig = np.insert(np.cumsum(r_fig_dist),0,0) + + #angles in figure coordinates + rads = np.arctan2((y_fig[1:] - y_fig[:-1]),(x_fig[1:] - x_fig[:-1])) + degs = np.rad2deg(rads) + + + rel_pos = 10 + for c,t in self.__Characters: + #finding the width of c: + t.set_rotation(0) + t.set_va('center') + bbox1 = t.get_window_extent(renderer=renderer) + w = bbox1.width + h = bbox1.height + + #ignore all letters that don't fit: + if rel_pos+w/2 > l_fig[-1]: + t.set_alpha(0.0) + rel_pos += w + continue + + elif c != ' ': + t.set_alpha(1.0) + + #finding the two data points between which the horizontal + #center point of the character will be situated + #left and right indices: + il = np.where(rel_pos+w/2 >= l_fig)[0][-1] + ir = np.where(rel_pos+w/2 <= l_fig)[0][0] + + #if we exactly hit a data point: + if ir == il: + ir += 1 + + #how much of the letter width was needed to find il: + used = l_fig[il]-rel_pos + rel_pos = l_fig[il] + + #relative distance between il and ir where the center + #of the character will be + fraction = (w/2-used)/r_fig_dist[il] + + ##setting the character position in data coordinates: + ##interpolate between the two points: + x = self.__x[il]+fraction*(self.__x[ir]-self.__x[il]) + y = self.__y[il]+fraction*(self.__y[ir]-self.__y[il]) + + #getting the offset when setting correct vertical alignment + #in data coordinates + t.set_va(self.get_va()) + bbox2 = t.get_window_extent(renderer=renderer) + + bbox1d = self.axes.transData.inverted().transform(bbox1) + bbox2d = self.axes.transData.inverted().transform(bbox2) + dr = np.array(bbox2d[0]-bbox1d[0]) + + #the rotation/stretch matrix + rad = rads[il] + rot_mat = np.array([ + [math.cos(rad), math.sin(rad)*aspect], + [-math.sin(rad)/aspect, math.cos(rad)] + ]) + + ##computing the offset vector of the rotated character + drp = np.dot(dr,rot_mat) + + #setting final position and rotation: + t.set_position(np.array([x,y])+drp) + t.set_rotation(degs[il]) + + t.set_va('center') + t.set_ha('center') + + #updating rel_pos to right edge of character + rel_pos += w-used \ No newline at end of file diff --git a/prettymaps/draw.py b/prettymaps/draw.py new file mode 100644 index 0000000..a920ffb --- /dev/null +++ b/prettymaps/draw.py @@ -0,0 +1,204 @@ +# OpenStreetMap Networkx library to download data from OpenStretMap +#from sympy import geometry +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, display +from collections.abc import Iterable + +# Fetch +from fetch import * + +# Helper functions +def get_hash(key): + return frozenset(key.items()) if type(key) == dict else key + +# 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) + if type(shape) == Polygon and shape.area > 0: + return PolygonPatch(list(zip(*shape.exterior.xy)), **kwargs) + else: + return None + +def plot_shape(shape, ax, vsketch = None, **kwargs): + ''' + Plot shapely object + ''' + if isinstance(shape, Iterable) and type(shape) != MultiLineString: + for shape_ in shape: + plot_shape(shape_, ax, vsketch = vsketch, **kwargs) + else: + if not shape.is_empty: + if vsketch is None: + ax.add_patch(PolygonPatch(shape, **kwargs)) + else: + if ('draw' not in kwargs) or kwargs['draw']: + + if ('pen' in kwargs): + vsketch.stroke(kwargs['pen']) + else: + vsketch.stroke(1) + + vsketch.geometry(shape) + +def plot_shapes(shapes, ax, vsketch = None, 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, vsketch = vsketch, **kwargs) + else: + plot_shape(shape, ax, vsketch = vsketch, fc = choice(palette), **kwargs) + +def plot( + # Address + query, + # Whether to use a backup for the layers + backup = None, + # Radius (in case of circular plot) + radius = None, + # Which layers to plot + layers = {'perimeter': {}}, + # Drawing params for each layer (matplotlib params such as 'fc', 'ec', 'fill', etc.) + drawing_kwargs = {}, + # Figure parameters + figsize = (10, 10), ax = None, title = None, + # Vsketch parameters + vsketch = None, + # Transform (translation & scale) params + x = None, y = None, sf = None, rotation = None, + ): + + # Interpret query + if type(query) == tuple: + query_mode = 'coordinates' + elif False: + query_mode = 'osmid' + else: + query_mode = 'address' + + # Save maximum dilation for later use + dilations = [kwargs['dilate'] for kwargs in layers.values() if 'dilate' in kwargs] + max_dilation = max(dilations) if len(dilations) > 0 else 0 + + if backup is None: + + ############# + ### Fetch ### + ############# + + # Define base kwargs + if radius: + base_kwargs = {'point': query if type(query) == tuple else ox.geocode(query), 'radius': radius} + else: + by_osmid = False + base_kwargs = {'perimeter': get_perimeter(query, by_osmid = by_osmid)} + + # Fetch layers + layers = { + layer: get_layer( + layer, + **base_kwargs, + **(kwargs if type(kwargs) == dict else {}) + ) + for layer, kwargs in layers.items() + } + + # Transform layers (translate & scale) + k, v = zip(*layers.items()) + v = GeometryCollection(v) + if (x is not None) and (y is not None): + v = translate(v, *(np.array([x, y]) - np.concatenate(v.centroid.xy))) + if sf is not None: + v = scale(v, sf, sf) + if rotation is not None: + v = rotate(v, rotation) + layers = dict(zip(k, v)) + + else: + layers = backup + + if vsketch is None: + # Ajust axis + ax.axis('off') + ax.axis('equal') + ax.autoscale() + + # Plot background + if 'background' in drawing_kwargs: + xmin, ymin, xmax, ymax = layers['perimeter'].bounds + geom = scale(Polygon([ + (xmin, ymin), + (xmin, ymax), + (xmax, ymax), + (xmax, ymin) + ]), 2, 2) + + if vsketch is None: + ax.add_patch(PolygonPatch(geom, **drawing_kwargs['background'])) + else: + vsketch.geometry(geom) + + ############ + ### Plot ### + ############ + + # Adjust bounds + xmin, ymin, xmax, ymax = layers['perimeter'].buffer(max_dilation).bounds + if vsketch is None: + ax.set_xlim(xmin, xmax) + ax.set_ylim(ymin, ymax) + + # Draw layers + for layer, shapes in layers.items(): + kwargs = drawing_kwargs[layer] if layer in drawing_kwargs else {} + if 'hatch_c' in kwargs: + plot_shapes(shapes, ax, vsketch = vsketch, lw = 0, ec = kwargs['hatch_c'], **{k:v for k,v in kwargs.items() if k not in ['lw', 'ec', 'hatch_c']}) + plot_shapes(shapes, ax, vsketch = vsketch, fill = False, **{k:v for k,v in kwargs.items() if k not in ['hatch_c', 'hatch', 'fill']}) + else: + plot_shapes(shapes, ax, vsketch = vsketch, **kwargs) + + # Return perimeter + return layers diff --git a/prettymaps/fetch.py b/prettymaps/fetch.py new file mode 100644 index 0000000..e3c1339 --- /dev/null +++ b/prettymaps/fetch.py @@ -0,0 +1,153 @@ +# OpenStreetMap Networkx library to download data from OpenStretMap +from ast import Mult +from operator import ge +import osmnx as ox + +# CV2 & Scipy & Numpy & Pandas +import numpy as np + +# Shapely +from shapely.geometry import * +from shapely.affinity import * +from shapely.ops import unary_union + +# Geopandas +from geopandas import GeoDataFrame + +# Matplotlib +from matplotlib.path import Path + +# etc +from collections.abc import Iterable +from functools import reduce +from descartes import PolygonPatch + +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_boundary(point, radius, crs, circle = True, dilate = 0): + if circle: + return ox.project_gdf( + GeoDataFrame(geometry = [Point(point[::-1])], crs = crs) + ).geometry[0].buffer(radius) + else: + x, y = np.stack(ox.project_gdf( + GeoDataFrame(geometry = [Point(point[::-1])], crs = crs) + ).geometry[0].xy) + r = radius + return Polygon([ + (x-r, y-r), (x+r, y-r), (x+r, y+r), (x-r, y+r) + ]).buffer(dilate) + +def get_perimeter(query, by_osmid = False, **kwargs): + return ox.geocode_to_gdf(query, by_osmid = by_osmid, **kwargs, **{x: kwargs[x] for x in ['circle', 'dilate'] if x in kwargs.keys()}) + +def get_geometries(perimeter = None, point = None, radius = None, tags = {}, perimeter_tolerance = 0, union = True, circle = True, dilate = 0): + + if perimeter is not None: + # Boundary defined by polygon (perimeter) + geometries = ox.geometries_from_polygon( + unary_union(perimeter.geometry).buffer(perimeter_tolerance) if perimeter_tolerance > 0 else unary_union(perimeter.geometry), + tags = {tags: True} if type(tags) == str else tags + ) + perimeter = unary_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 + geometries = ox.geometries_from_point(point, dist = radius+dilate, tags = {tags: True} if type(tags) == str else tags) + perimeter = get_boundary(point, radius, geometries.crs, circle = circle, dilate = dilate) + + # Project GDF + if len(geometries) > 0: + geometries = ox.project_gdf(geometries) + + # Intersect with perimeter + geometries = geometries.intersection(perimeter) + + if union: + geometries = unary_union(reduce(lambda x,y: x+y, [ + [x] if type(x) == Polygon else list(x) + for x in geometries if type(x) in [Polygon, MultiPolygon] + ], [])) + else: + geometries = MultiPolygon(reduce(lambda x,y: x+y, [ + [x] if type(x) == Polygon else list(x) + for x in geometries if type(x) in [Polygon, MultiPolygon] + ], [])) + + return geometries + +def get_streets(perimeter = None, point = None, radius = None, width = 6, custom_filter = None, circle = True, dilate = 0): + + # Boundary defined by polygon (perimeter) + if perimeter is not None: + # Fetch streets data, project & convert to GDF + 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) + # Boundary defined by polygon (perimeter) + elif (point is not None) and (radius is not None): + # Fetch streets data, save CRS & project + streets = ox.graph_from_point(point, dist = radius+dilate, custom_filter = custom_filter) + crs = ox.graph_to_gdfs(streets, nodes = False).crs + streets = ox.project_graph(streets) + # Compute perimeter from point & CRS + perimeter = get_boundary(point, radius, crs, circle = circle, dilate = dilate) + # Convert to GDF + streets = ox.graph_to_gdfs(streets, nodes = False) + # Intersect with perimeter & filter empty elements + streets.geometry = streets.geometry.intersection(perimeter) + streets = streets[~streets.geometry.is_empty] + + if type(width) == dict: + streets = unary_union([ + # Dilate streets of each highway type == 'highway' using width 'w' + MultiLineString( + streets[(streets.highway == highway) & (streets.geometry.type == 'LineString')].geometry.tolist() + + list(reduce(lambda x, y: x+y, [ + list(lines) + for lines in streets[(streets.highway == highway) & (streets.geometry.type == 'MultiLineString')].geometry + ], [])) + ).buffer(w) + for highway, w in width.items() + ]) + else: + # Dilate all streets by same amount 'width' + streets = MultiLineString(streets.geometry.tolist()).buffer(width) + + return streets + +def get_layer(layer, **kwargs): + if layer == 'perimeter': + if 'perimeter' in kwargs: + return unary_union(ox.project_gdf(kwargs['perimeter']).geometry) + elif 'point' in kwargs and 'radius' in kwargs: + # Dummy request to fetch CRS + crs = ox.graph_to_gdfs(ox.graph_from_point(kwargs['point'], dist = kwargs['radius']), nodes = False).crs + perimeter = get_boundary( + kwargs['point'], kwargs['radius'], crs, + **{x: kwargs[x] for x in ['circle', 'dilate'] if x in kwargs.keys()} + ) + return perimeter + else: + raise Exception("Either 'perimeter' or 'point' & 'radius' must be provided") + if layer in ['streets', 'railway']: + return get_streets(**kwargs) + else: + return get_geometries(**kwargs) \ No newline at end of file diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..509cae0 --- /dev/null +++ b/setup.py @@ -0,0 +1,21 @@ +from setuptools import setup + +setup( + name='prettymaps', + version='1.0.0', + description='A simple python library to draw pretty maps from OpenStreetMap data', + url='https://github.com/marceloprates/prettymaps', + author='Marcelo Prates', + author_email='marceloorp@gmail.com', + license='MIT License', + packages=['prettymaps'], + install_requires=[ + 'osmnx=1.0.1', + 'tabulate=0.8.9', + 'jupyter=1.0.0', + ], + + classifiers=[ + 'Intended Audience :: Science/Research', + ], +)