From 29f57e894b6aa6111860b1a67e17a47ebfca56f0 Mon Sep 17 00:00:00 2001 From: Christoph Rieke Date: Mon, 30 Aug 2021 14:32:43 +0200 Subject: [PATCH 1/2] use shapely.geometry.box for polygon from bounds --- prettymaps/draw.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/prettymaps/draw.py b/prettymaps/draw.py index 6d715a1..f6f33d4 100644 --- a/prettymaps/draw.py +++ b/prettymaps/draw.py @@ -6,7 +6,7 @@ import pandas as pd from geopandas import GeoDataFrame import numpy as np from numpy.random import choice -from shapely.geometry import Polygon, MultiPolygon, MultiLineString, GeometryCollection +from shapely.geometry import box, Polygon, MultiLineString, GeometryCollection from shapely.affinity import translate, scale, rotate from descartes import PolygonPatch from tabulate import tabulate @@ -202,13 +202,7 @@ def plot( # 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) + geom = scale(box(*layers['perimeter'].bounds), 2, 2) if vsketch is None: ax.add_patch(PolygonPatch(geom, **drawing_kwargs['background'])) From 8312319f511c0d3bc2321139f7c39576b01da0c7 Mon Sep 17 00:00:00 2001 From: Christoph Rieke Date: Mon, 30 Aug 2021 15:28:51 +0200 Subject: [PATCH 2/2] apply black code formatting --- prettymaps/__init__.py | 2 +- prettymaps/curved_text.py | 132 +++++++++++++------------- prettymaps/draw.py | 176 ++++++++++++++++++++++------------- prettymaps/fetch.py | 189 +++++++++++++++++++++++++++----------- setup.py | 18 ++-- 5 files changed, 323 insertions(+), 194 deletions(-) diff --git a/prettymaps/__init__.py b/prettymaps/__init__.py index 5adf9f6..4206a76 100644 --- a/prettymaps/__init__.py +++ b/prettymaps/__init__.py @@ -1 +1 @@ -from .draw import plot \ No newline at end of file +from .draw import plot diff --git a/prettymaps/curved_text.py b/prettymaps/curved_text.py index 9adb2de..7448372 100644 --- a/prettymaps/curved_text.py +++ b/prettymaps/curved_text.py @@ -8,8 +8,9 @@ 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) + super(CurvedText, self).__init__(x[0], y[0], " ", **kwargs) axes.add_artist(self) @@ -21,29 +22,28 @@ class CurvedText(mtext.Text): ##creating the text objects self.__Characters = [] for c in text: - if c == ' ': + if c == " ": ##make this an invisible 'a': - t = mtext.Text(0,0,'a') + t = mtext.Text(0, 0, "a") t.set_alpha(0.0) else: - t = mtext.Text(0,0,c, **kwargs) + t = mtext.Text(0, 0, c, **kwargs) - #resetting unnecessary arguments - t.set_ha('center') + # resetting unnecessary arguments + t.set_ha("center") t.set_rotation(0) - t.set_zorder(self.__zorder +1) + t.set_zorder(self.__zorder + 1) - self.__Characters.append((c,t)) + 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) + for c, t in self.__Characters: + t.set_zorder(self.__zorder + 1) def draw(self, renderer, *args, **kwargs): """ @@ -53,12 +53,12 @@ class CurvedText(mtext.Text): """ self.update_positions(renderer) - def update_positions(self,renderer): + def update_positions(self, renderer): """ Update positions and rotations of the individual text elements. """ - #preparations + # preparations ##determining the aspect ratio: ##from https://stackoverflow.com/a/42014041/2454357 @@ -71,94 +71,98 @@ class CurvedText(mtext.Text): ## 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]) + 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) - ])) + # 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) + # 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) + # 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])) + # 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: + 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) + 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]: + # 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 != ' ': + 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] + # 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 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 + # 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] + # 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]) + 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 + # getting the offset when setting correct vertical alignment + # in data coordinates t.set_va(self.get_va()) - bbox2 = t.get_window_extent(renderer=renderer) + 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]) + dr = np.array(bbox2d[0] - bbox1d[0]) - #the rotation/stretch matrix + # 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)] - ]) + 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) + drp = np.dot(dr, rot_mat) - #setting final position and rotation: - t.set_position(np.array([x,y])+drp) + # 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') + 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 + # updating rel_pos to right edge of character + rel_pos += w - used diff --git a/prettymaps/draw.py b/prettymaps/draw.py index f6f33d4..bf0de08 100644 --- a/prettymaps/draw.py +++ b/prettymaps/draw.py @@ -19,88 +19,93 @@ from .fetch import get_perimeter, get_layer def get_hash(key): return frozenset(key.items()) if type(key) == dict else key + # Drawing functions -def show_palette(palette, description = ''): - ''' +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 + 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))) + display(Markdown(tabulate(pd.DataFrame(colorboxes), showindex=False))) + def get_patch(shape, **kwargs): - ''' + """ Convert shapely object to matplotlib patch - ''' - #if type(shape) == Path: + """ + # 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 + # Plot a single shape -def plot_shape(shape, ax, vsketch = None, **kwargs): - ''' +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) + 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 ("draw" not in kwargs) or kwargs["draw"]: - if 'stroke' in kwargs: - vsketch.stroke(kwargs['stroke']) + if "stroke" in kwargs: + vsketch.stroke(kwargs["stroke"]) else: vsketch.stroke(1) - if 'penWidth' in kwargs: - vsketch.penWidth(kwargs['penWidth']) + if "penWidth" in kwargs: + vsketch.penWidth(kwargs["penWidth"]) else: vsketch.penWidth(0.3) - if 'fill' in kwargs: - vsketch.fill(kwargs['fill']) + if "fill" in kwargs: + vsketch.fill(kwargs["fill"]) else: vsketch.noFill() vsketch.geometry(shape) + # Plot a collection of shapes -def plot_shapes(shapes, ax, vsketch = None, palette = None, **kwargs): - ''' +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) + plot_shape(shape, ax, vsketch=vsketch, **kwargs) else: - plot_shape(shape, ax, vsketch = vsketch, fc = choice(palette), **kwargs) + plot_shape(shape, ax, vsketch=vsketch, fc=choice(palette), **kwargs) + # Parse query (by coordinates, OSMId or name) def parse_query(query): if isinstance(query, GeoDataFrame): - return 'polygon' + return "polygon" elif isinstance(query, tuple): - return 'coordinates' - elif re.match('''[A-Z][0-9]+''', query): - return 'osmid' + return "coordinates" + elif re.match("""[A-Z][0-9]+""", query): + return "osmid" else: - return 'address' + return "address" + # Apply transformation (translation & scale) to layers def transform(layers, x, y, scale_x, scale_y, rotation): @@ -118,38 +123,46 @@ def transform(layers, x, y, scale_x, scale_y, rotation): layers = dict(zip(k, v)) return layers + def draw_text(ax, text, x, y, **kwargs): ax.text(x, y, text, **kwargs) + # Plot def plot( # Address query, # Whether to use a backup for the layers - backup = None, + backup=None, # Custom postprocessing function on layers - postprocessing = None, + postprocessing=None, # Radius (in case of circular plot) - radius = None, + radius=None, # Which layers to plot - layers = {'perimeter': {}}, + layers={"perimeter": {}}, # Drawing params for each layer (matplotlib params such as 'fc', 'ec', 'fill', etc.) - drawing_kwargs = {}, + drawing_kwargs={}, # OSM Caption parameters - osm_credit = {}, + osm_credit={}, # Figure parameters - figsize = (10, 10), ax = None, title = None, + figsize=(10, 10), + ax=None, + title=None, # Vsketch parameters - vsketch = None, + vsketch=None, # Transform (translation & scale) params - x = None, y = None, scale_x = None, scale_y = None, rotation = None, - ): + x=None, + y=None, + scale_x=None, + scale_y=None, + rotation=None, +): # Interpret query query_mode = parse_query(query) # Save maximum dilation for later use - dilations = [kwargs['dilate'] for kwargs in layers.values() if 'dilate' in kwargs] + dilations = [kwargs["dilate"] for kwargs in layers.values() if "dilate" in kwargs] max_dilation = max(dilations) if len(dilations) > 0 else 0 #################### @@ -164,20 +177,20 @@ def plot( # Define base kwargs if radius: base_kwargs = { - 'point': query if query_mode == 'coordinates' else ox.geocode(query), - 'radius': radius + "point": query if query_mode == "coordinates" else ox.geocode(query), + "radius": radius, } else: base_kwargs = { - 'perimeter': query if query_mode == 'polygon' else get_perimeter(query, by_osmid = query_mode == 'osmid') + "perimeter": query + if query_mode == "polygon" + else get_perimeter(query, by_osmid=query_mode == "osmid") } # Fetch layers layers = { layer: get_layer( - layer, - **base_kwargs, - **(kwargs if type(kwargs) == dict else {}) + layer, **base_kwargs, **(kwargs if type(kwargs) == dict else {}) ) for layer, kwargs in layers.items() } @@ -196,22 +209,22 @@ def plot( # Matplot-specific stuff (only run if vsketch mode isn't activated) if vsketch is None: # Ajust axis - ax.axis('off') - ax.axis('equal') + ax.axis("off") + ax.axis("equal") ax.autoscale() # Plot background - if 'background' in drawing_kwargs: - geom = scale(box(*layers['perimeter'].bounds), 2, 2) + if "background" in drawing_kwargs: + geom = scale(box(*layers["perimeter"].bounds), 2, 2) if vsketch is None: - ax.add_patch(PolygonPatch(geom, **drawing_kwargs['background'])) + ax.add_patch(PolygonPatch(geom, **drawing_kwargs["background"])) else: vsketch.geometry(geom) # Adjust bounds - xmin, ymin, xmax, ymax = layers['perimeter'].buffer(max_dilation).bounds - dx, dy = xmax-xmin, ymax-ymin + xmin, ymin, xmax, ymax = layers["perimeter"].buffer(max_dilation).bounds + dx, dy = xmax - xmin, ymax - ymin if vsketch is None: ax.set_xlim(xmin, xmax) ax.set_ylim(ymin, ymax) @@ -219,27 +232,58 @@ def plot( # Draw layers for layer, shapes in layers.items(): kwargs = drawing_kwargs[layer] if layer in drawing_kwargs else {} - if 'hatch_c' in kwargs: + if "hatch_c" in kwargs: # Draw hatched shape - 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, + lw=0, + ec=kwargs["hatch_c"], + **{k: v for k, v in kwargs.items() if k not in ["lw", "ec", "hatch_c"]}, + ) # Draw shape contour only - plot_shapes(shapes, ax, vsketch = vsketch, fill = False, **{k:v for k,v in kwargs.items() if k not in ['hatch_c', 'hatch', 'fill']}) + 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: # Draw shape normally - plot_shapes(shapes, ax, vsketch = vsketch, **kwargs) + plot_shapes(shapes, ax, vsketch=vsketch, **kwargs) if ((isinstance(osm_credit, dict)) or (osm_credit is True)) and (vsketch is None): x, y = figsize - d = .8*(x**2+y**2)**.5 + d = 0.8 * (x ** 2 + y ** 2) ** 0.5 draw_text( ax, - (osm_credit['text'] if 'text' in osm_credit else 'data © OpenStreetMap contributors\ngithub.com/marceloprates/prettymaps'), - x = xmin + (osm_credit['x']*dx if 'x' in osm_credit else 0), - y = ymax - 4*d - (osm_credit['y']*dy if 'y' in osm_credit else 0), - fontfamily = (osm_credit['fontfamily'] if 'fontfamily' in osm_credit else 'Ubuntu Mono'), - fontsize = (osm_credit['fontsize']*d if 'fontsize' in osm_credit else d), - zorder = (osm_credit['zorder'] if 'zorder' in osm_credit else len(layers)+1), - **{k:v for k,v in osm_credit.items() if k not in ['text', 'x', 'y', 'fontfamily', 'fontsize', 'zorder']} + ( + osm_credit["text"] + if "text" in osm_credit + else "data © OpenStreetMap contributors\ngithub.com/marceloprates/prettymaps" + ), + x=xmin + (osm_credit["x"] * dx if "x" in osm_credit else 0), + y=ymax - 4 * d - (osm_credit["y"] * dy if "y" in osm_credit else 0), + fontfamily=( + osm_credit["fontfamily"] + if "fontfamily" in osm_credit + else "Ubuntu Mono" + ), + fontsize=(osm_credit["fontsize"] * d if "fontsize" in osm_credit else d), + zorder=( + osm_credit["zorder"] if "zorder" in osm_credit else len(layers) + 1 + ), + **{ + k: v + for k, v in osm_credit.items() + if k not in ["text", "x", "y", "fontfamily", "fontsize", "zorder"] + }, ) # Return perimeter diff --git a/prettymaps/fetch.py b/prettymaps/fetch.py index fd55fcd..cf6d19e 100644 --- a/prettymaps/fetch.py +++ b/prettymaps/fetch.py @@ -8,39 +8,67 @@ from geopandas import GeoDataFrame # Compute circular or square boundary given point, radius and crs -def get_boundary(point, radius, crs, circle = True, dilate = 0): +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) + 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) + 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) + return Polygon( + [(x - r, y - r), (x + r, y - r), (x + r, y + r), (x - r, y + r)] + ).buffer(dilate) + # Get perimeter -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_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()} + ) + # Get geometries -def get_geometries(perimeter = None, point = None, radius = None, tags = {}, perimeter_tolerance = 0, union = True, circle = True, dilate = 0): +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 + 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) + 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: @@ -50,82 +78,135 @@ def get_geometries(perimeter = None, point = None, radius = None, tags = {}, per 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] - ], [])) + 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] - ], [])) + 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 -# Get streets -def get_streets(perimeter = None, point = None, radius = None, layer = 'streets', width = 6, custom_filter = None, buffer = 0, retain_all = False, circle = True, dilate = 0): - if layer == 'streets': - layer = 'highway' +# Get streets +def get_streets( + perimeter=None, + point=None, + radius=None, + layer="streets", + width=6, + custom_filter=None, + buffer=0, + retain_all=False, + circle=True, + dilate=0, +): + + if layer == "streets": + layer = "highway" # Boundary defined by polygon (perimeter) if perimeter is not None: # Fetch streets data, project & convert to GDF - streets = ox.graph_from_polygon(unary_union(perimeter.geometry).buffer(buffer) if buffer > 0 else unary_union(perimeter.geometry), custom_filter = custom_filter) + streets = ox.graph_from_polygon( + unary_union(perimeter.geometry).buffer(buffer) + if buffer > 0 + else unary_union(perimeter.geometry), + custom_filter=custom_filter, + ) streets = ox.project_graph(streets) - streets = ox.graph_to_gdfs(streets, nodes = False) + 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+buffer, retain_all = retain_all, custom_filter = custom_filter) - crs = ox.graph_to_gdfs(streets, nodes = False).crs + streets = ox.graph_from_point( + point, + dist=radius + dilate + buffer, + retain_all=retain_all, + 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) + perimeter = get_boundary(point, radius, crs, circle=circle, dilate=dilate) # Convert to GDF - streets = ox.graph_to_gdfs(streets, nodes = False) + 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[layer] == highway) & (streets.geometry.type == 'LineString')].geometry.tolist() + - list(reduce(lambda x, y: x+y, [ - list(lines) - for lines in streets[(streets[layer] == highway) & (streets.geometry.type == 'MultiLineString')].geometry - ], [])) - ).buffer(w) - for highway, w in width.items() - ]) + streets = unary_union( + [ + # Dilate streets of each highway type == 'highway' using width 'w' + MultiLineString( + streets[ + (streets[layer] == highway) + & (streets.geometry.type == "LineString") + ].geometry.tolist() + + list( + reduce( + lambda x, y: x + y, + [ + list(lines) + for lines in streets[ + (streets[layer] == 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 + # Get any layer def get_layer(layer, **kwargs): # Fetch perimeter - if layer == 'perimeter': + if layer == "perimeter": # If perimeter is already provided: - if 'perimeter' in kwargs: - return unary_union(ox.project_gdf(kwargs['perimeter']).geometry) + if "perimeter" in kwargs: + return unary_union(ox.project_gdf(kwargs["perimeter"]).geometry) # If point and radius are provided: - elif 'point' in kwargs and 'radius' in kwargs: + elif "point" in kwargs and "radius" in kwargs: crs = "EPSG:4326" perimeter = get_boundary( - kwargs['point'], kwargs['radius'], crs, - **{x: kwargs[x] for x in ['circle', 'dilate'] if x in kwargs.keys()} + 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") # Fetch streets or railway - if layer in ['streets', 'railway', 'waterway']: - return get_streets(**kwargs, layer = layer) + if layer in ["streets", "railway", "waterway"]: + return get_streets(**kwargs, layer=layer) # Fetch geometries else: return get_geometries(**kwargs) diff --git a/setup.py b/setup.py index a523242..2b0f787 100644 --- a/setup.py +++ b/setup.py @@ -4,18 +4,18 @@ from pathlib import Path parent_dir = Path(__file__).resolve().parent setup( - name='prettymaps', - version='1.0.0', - description='A simple python library to draw pretty maps from OpenStreetMap data', + name="prettymaps", + version="1.0.0", + description="A simple python library to draw pretty maps from OpenStreetMap data", long_description=parent_dir.joinpath("README.md").read_text(), long_description_content_type="text/markdown", - url='https://github.com/marceloprates/prettymaps', - author='Marcelo Prates', - author_email='marceloorp@gmail.com', - license='MIT License', + url="https://github.com/marceloprates/prettymaps", + author="Marcelo Prates", + author_email="marceloorp@gmail.com", + license="MIT License", packages=find_packages(exclude=("assets", "notebooks", "prints", "script")), install_requires=parent_dir.joinpath("requirements.txt").read_text().splitlines(), classifiers=[ - 'Intended Audience :: Science/Research', + "Intended Audience :: Science/Research", ], -) +)