From 53d451d9ea02164e327dcbff06d4402d6601cb99 Mon Sep 17 00:00:00 2001 From: marceloprates Date: Sat, 17 May 2025 01:18:32 +0000 Subject: [PATCH] deploy: 20858a6f5868bc4a65b5e251e45d5111661c3a15 --- api/index.html | 5717 +++++++++++++++++++++++++++++++++++++- index.html | 94 +- objects.inv | Bin 132 -> 450 bytes search/search_index.json | 2 +- usage/index.html | 169 +- 5 files changed, 5976 insertions(+), 6 deletions(-) diff --git a/api/index.html b/api/index.html index 3f8ef0f..b5a88a4 100644 --- a/api/index.html +++ b/api/index.html @@ -257,6 +257,19 @@ + + @@ -269,6 +282,337 @@ + + + + @@ -293,6 +637,326 @@ + + + @@ -308,8 +972,5057 @@

API Reference

-

This page will contain the API reference for Prettymaps.

-

More details coming soon!

+

This section is auto-generated from the Prettymaps source code.

+ + +
+ + + +

+ prettymaps.draw + + +

+ +
+ +

Prettymaps - A minimal Python library to draw pretty maps from OpenStreetMap Data +Copyright (C) 2021 Marcelo Prates

+

This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published +by the Free Software Foundation, either version 3 of the License, or +(at your option) any later version.

+

This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details.

+

You should have received a copy of the GNU Affero General Public License +along with this program. If not, see https://www.gnu.org/licenses/.

+ + + + + + + + + +
+ + + + + + + + +
+ + + +

+ Plot + + + + dataclass + + +

+ + +
+ + +

Dataclass implementing a prettymaps Plot object.

+ + +

Attributes:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
geodataframes + Dict[str, GeoDataFrame] + +
+

A dictionary of GeoDataFrames (one for each plot layer).

+
+
fig + Figure + +
+

A matplotlib figure.

+
+
ax + Axes + +
+

A matplotlib axis object.

+
+
background + BaseGeometry + +
+

Background layer (shapely object).

+
+
keypoints + GeoDataFrame + +
+

Keypoints GeoDataFrame.

+
+
+ + + + + + + +
+ Source code in prettymaps/draw.py +
 83
+ 84
+ 85
+ 86
+ 87
+ 88
+ 89
+ 90
+ 91
+ 92
+ 93
+ 94
+ 95
+ 96
+ 97
+ 98
+ 99
+100
@dataclass
+class Plot:
+    """
+    Dataclass implementing a prettymaps Plot object.
+
+    Attributes:
+        geodataframes (Dict[str, gp.GeoDataFrame]): A dictionary of GeoDataFrames (one for each plot layer).
+        fig (matplotlib.figure.Figure): A matplotlib figure.
+        ax (matplotlib.axes.Axes): A matplotlib axis object.
+        background (BaseGeometry): Background layer (shapely object).
+        keypoints (gp.GeoDataFrame): Keypoints GeoDataFrame.
+    """
+
+    geodataframes: Dict[str, gp.GeoDataFrame]
+    fig: matplotlib.figure.Figure
+    ax: matplotlib.axes.Axes
+    background: BaseGeometry
+    keypoints: gp.GeoDataFrame
+
+
+ + + +
+ + + + + + + + + + + +
+ +
+ +
+ +
+ + + +

+ PolygonPatch + + +

+ + +
+

+ Bases: PathPatch

+ + +

A class to create a matplotlib PathPatch from a shapely geometry.

+ + +

Attributes:

+ + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
shape + BaseGeometry + +
+

Shapely geometry.

+
+
kwargs + BaseGeometry + +
+

Parameters for matplotlib's PathPatch constructor.

+
+
+ + +

Methods:

+ + + + + + + + + + + + + +
NameDescription
__init__ +
+

BaseGeometry, **kwargs): +Initialize the PolygonPatch with the given shapely geometry and additional parameters.

+

shape (BaseGeometry): Shapely geometry. +kwargs: Parameters for matplotlib's PathPatch constructor.

+
+
+ + + + + + + +
+ Source code in prettymaps/draw.py +
115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
class PolygonPatch(PathPatch):
+    """
+    A class to create a matplotlib PathPatch from a shapely geometry.
+
+    Attributes:
+        shape (BaseGeometry): Shapely geometry.
+        kwargs: Parameters for matplotlib's PathPatch constructor.
+
+    Methods:
+        __init__(shape: BaseGeometry, **kwargs):
+            Initialize the PolygonPatch with the given shapely geometry and additional parameters.
+
+
+            shape (BaseGeometry): Shapely geometry.
+            kwargs: Parameters for matplotlib's PathPatch constructor.
+    """
+
+    def __init__(self, shape: BaseGeometry, **kwargs):
+        """
+        Initialize the PolygonPatch.
+
+        Args:
+            shape (BaseGeometry): Shapely geometry
+            kwargs: parameters for matplotlib's PathPatch constructor
+        """
+        # Init vertices and codes lists
+        vertices, codes = [], []
+        for geom in shape.geoms if hasattr(shape, "geoms") else [shape]:
+            for poly in geom.geoms if hasattr(geom, "geoms") else [geom]:
+                if type(poly) != Polygon:
+                    continue
+                # Get polygon's exterior and interiors
+                exterior = np.array(poly.exterior.xy)
+                interiors = [np.array(interior.xy) for interior in poly.interiors]
+                # Append to vertices and codes lists
+                vertices += [exterior] + interiors
+                codes += list(
+                    map(
+                        # Ring coding
+                        lambda p: [Path.MOVETO]
+                        + [Path.LINETO] * (p.shape[1] - 2)
+                        + [Path.CLOSEPOLY],
+                        [exterior] + interiors,
+                    )
+                )
+        # Initialize PathPatch with the generated Path
+        super().__init__(
+            Path(np.concatenate(vertices, 1).T, np.concatenate(codes)), **kwargs
+        )
+
+
+ + + +
+ + + + + + + + + +
+ + +

+ __init__(shape, **kwargs) + +

+ + +
+ +

Initialize the PolygonPatch.

+ + +

Parameters:

+ + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
+ shape + + BaseGeometry + +
+

Shapely geometry

+
+
+ required +
+ kwargs + + +
+

parameters for matplotlib's PathPatch constructor

+
+
+ {} +
+ + +
+ Source code in prettymaps/draw.py +
132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
def __init__(self, shape: BaseGeometry, **kwargs):
+    """
+    Initialize the PolygonPatch.
+
+    Args:
+        shape (BaseGeometry): Shapely geometry
+        kwargs: parameters for matplotlib's PathPatch constructor
+    """
+    # Init vertices and codes lists
+    vertices, codes = [], []
+    for geom in shape.geoms if hasattr(shape, "geoms") else [shape]:
+        for poly in geom.geoms if hasattr(geom, "geoms") else [geom]:
+            if type(poly) != Polygon:
+                continue
+            # Get polygon's exterior and interiors
+            exterior = np.array(poly.exterior.xy)
+            interiors = [np.array(interior.xy) for interior in poly.interiors]
+            # Append to vertices and codes lists
+            vertices += [exterior] + interiors
+            codes += list(
+                map(
+                    # Ring coding
+                    lambda p: [Path.MOVETO]
+                    + [Path.LINETO] * (p.shape[1] - 2)
+                    + [Path.CLOSEPOLY],
+                    [exterior] + interiors,
+                )
+            )
+    # Initialize PathPatch with the generated Path
+    super().__init__(
+        Path(np.concatenate(vertices, 1).T, np.concatenate(codes)), **kwargs
+    )
+
+
+
+ +
+ + + +
+ +
+ +
+ +
+ + + +

+ Preset + + + + dataclass + + +

+ + +
+ + +

Dataclass implementing a prettymaps Preset object.

+ + +

Attributes:

+ + + + + + + + + + + + + + + +
NameTypeDescription
params + dict + +
+

Dictionary of prettymaps.plot() parameters.

+
+
+ + + + + + + +
+ Source code in prettymaps/draw.py +
103
+104
+105
+106
+107
+108
+109
+110
+111
+112
@dataclass
+class Preset:
+    """
+    Dataclass implementing a prettymaps Preset object.
+
+    Attributes:
+        params (dict): Dictionary of prettymaps.plot() parameters.
+    """
+
+    params: dict
+
+
+ + + +
+ + + + + + + + + + + +
+ +
+ +
+ +
+ + + +

+ Subplot + + +

+ + +
+ + +

Class implementing a prettymaps Subplot. Attributes: +- query: prettymaps.plot() query +- kwargs: dictionary of prettymaps.plot() parameters

+ + + + + + + +
+ Source code in prettymaps/draw.py +
71
+72
+73
+74
+75
+76
+77
+78
+79
+80
class Subplot:
+    """
+    Class implementing a prettymaps Subplot. Attributes:
+    - query: prettymaps.plot() query
+    - kwargs: dictionary of prettymaps.plot() parameters
+    """
+
+    def __init__(self, query, **kwargs):
+        self.query = query
+        self.kwargs = kwargs
+
+
+ + + +
+ + + + + + + + + + + +
+ +
+ +
+ + +
+ + +

+ create_background(gdfs, style, logging=False) + +

+ + +
+ +

Create a background layer given a collection of GeoDataFrames

+ + +

Parameters:

+ + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
+ gdfs + + Dict[str, GeoDataFrame] + +
+

Dictionary of GeoDataFrames

+
+
+ required +
+ style + + Dict[str, dict] + +
+

Dictionary of matplotlib style parameters

+
+
+ required +
+ + +

Returns:

+ + + + + + + + + + + + + +
TypeDescription
+ Tuple[BaseGeometry, float, float, float, float, float, float] + +
+

Tuple[BaseGeometry, float, float, float, float, float, float]: background geometry, bounds, width and height

+
+
+ + +
+ Source code in prettymaps/draw.py +
580
+581
+582
+583
+584
+585
+586
+587
+588
+589
+590
+591
+592
+593
+594
+595
+596
+597
+598
+599
+600
+601
+602
+603
+604
+605
+606
+607
+608
+609
+610
+611
+612
+613
+614
+615
+616
+617
@log_execution_time
+def create_background(
+    gdfs: Dict[str, gp.GeoDataFrame],
+    style: Dict[str, dict],
+    logging=False,
+) -> Tuple[BaseGeometry, float, float, float, float, float, float]:
+    """
+    Create a background layer given a collection of GeoDataFrames
+
+    Args:
+        gdfs (Dict[str, gp.GeoDataFrame]): Dictionary of GeoDataFrames
+        style (Dict[str, dict]): Dictionary of matplotlib style parameters
+
+    Returns:
+        Tuple[BaseGeometry, float, float, float, float, float, float]: background geometry, bounds, width and height
+    """
+
+    # Create background
+    background_pad = 1.1
+    if "background" in style and "pad" in style["background"]:
+        background_pad = style["background"].pop("pad")
+
+    background = shapely.affinity.scale(
+        box(
+            *shapely.ops.unary_union(ox.project_gdf(gdfs["perimeter"]).geometry).bounds
+        ),
+        background_pad,
+        background_pad,
+    )
+
+    if "background" in style and "dilate" in style["background"]:
+        background = background.buffer(style["background"].pop("dilate"))
+
+    # Get bounds
+    xmin, ymin, xmax, ymax = background.bounds
+    dx, dy = xmax - xmin, ymax - ymin
+
+    return background, xmin, ymin, xmax, ymax, dx, dy
+
+
+
+ +
+ +
+ + +

+ create_preset(name, layers=None, style=None, circle=None, radius=None, dilate=None) + +

+ + +
+ +

Create a preset file and save it on the presets folder (prettymaps/presets/) under name 'name.json'

+ + +

Parameters:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
+ name + + str + +
+

Preset name

+
+
+ required +
+ layers + + Dict[str, dict] + +
+

prettymaps.plot() 'layers' parameter dict. Defaults to None.

+
+
+ None +
+ style + + Dict[str, dict] + +
+

prettymaps.plot() 'style' parameter dict. Defaults to None.

+
+
+ None +
+ circle + + Optional[bool] + +
+

prettymaps.plot() 'circle' parameter. Defaults to None.

+
+
+ None +
+ radius + + Optional[Union[float, bool]] + +
+

prettymaps.plot() 'radius' parameter. Defaults to None.

+
+
+ None +
+ dilate + + Optional[Union[float, bool]] + +
+

prettymaps.plot() 'dilate' parameter. Defaults to None.

+
+
+ None +
+ + +
+ Source code in prettymaps/draw.py +
710
+711
+712
+713
+714
+715
+716
+717
+718
+719
+720
+721
+722
+723
+724
+725
+726
+727
+728
+729
+730
+731
+732
+733
+734
+735
+736
+737
+738
+739
+740
+741
+742
+743
+744
+745
def create_preset(
+    name: str,
+    layers: Optional[Dict[str, dict]] = None,
+    style: Optional[Dict[str, dict]] = None,
+    circle: Optional[bool] = None,
+    radius: Optional[Union[float, bool]] = None,
+    dilate: Optional[Union[float, bool]] = None,
+) -> None:
+    """
+    Create a preset file and save it on the presets folder (prettymaps/presets/) under name 'name.json'
+
+    Args:
+        name (str): Preset name
+        layers (Dict[str, dict], optional): prettymaps.plot() 'layers' parameter dict. Defaults to None.
+        style (Dict[str, dict], optional): prettymaps.plot() 'style' parameter dict. Defaults to None.
+        circle (Optional[bool], optional): prettymaps.plot() 'circle' parameter. Defaults to None.
+        radius (Optional[Union[float, bool]], optional): prettymaps.plot() 'radius' parameter. Defaults to None.
+        dilate (Optional[Union[float, bool]], optional): prettymaps.plot() 'dilate' parameter. Defaults to None.
+    """
+
+    # if not os.path.isdir('presets'):
+    #    os.makedirs('presets')
+
+    path = os.path.join(presets_directory(), f"{name}.json")
+    with open(path, "w") as f:
+        json.dump(
+            {
+                "layers": layers,
+                "style": style,
+                "circle": circle,
+                "radius": radius,
+                "dilate": dilate,
+            },
+            f,
+            ensure_ascii=False,
+        )
+
+
+
+ +
+ +
+ + +

+ draw_credit(ax, background, credit, mode, multiplot, logging=False) + +

+ + +
+ +

Draws credit text on the plot.

+ + +

Parameters:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
+ ax + + Axes + +
+

Matplotlib axis object.

+
+
+ required +
+ background + + BaseGeometry + +
+

Background layer.

+
+
+ required +
+ credit + + Dict[str, Any] + +
+

Dictionary containing credit text and style parameters.

+
+
+ required +
+ mode + + str + +
+

Drawing mode. Options: 'matplotlib', 'plotter'.

+
+
+ required +
+ multiplot + + bool + +
+

Whether the plot is part of a multiplot.

+
+
+ required +
+ logging + + bool + +
+

Whether to enable logging. Defaults to False.

+
+
+ False +
+ + +
+ Source code in prettymaps/draw.py +
946
+947
+948
+949
+950
+951
+952
+953
+954
+955
+956
+957
+958
+959
+960
+961
+962
+963
+964
+965
+966
+967
@log_execution_time
+def draw_credit(
+    ax: matplotlib.axes.Axes,
+    background: BaseGeometry,
+    credit: Dict[str, Any],
+    mode: str,
+    multiplot: bool,
+    logging: bool = False,
+) -> None:
+    """
+    Draws credit text on the plot.
+
+    Args:
+        ax (matplotlib.axes.Axes): Matplotlib axis object.
+        background (BaseGeometry): Background layer.
+        credit (Dict[str, Any]): Dictionary containing credit text and style parameters.
+        mode (str): Drawing mode. Options: 'matplotlib', 'plotter'.
+        multiplot (bool): Whether the plot is part of a multiplot.
+        logging (bool, optional): Whether to enable logging. Defaults to False.
+    """
+    if (mode == "matplotlib") and (credit != False) and (not multiplot):
+        draw_text(ax, credit, background)
+
+
+
+ +
+ +
+ + +

+ draw_text(ax, params, background) + +

+ + +
+ +

Draw text with content and matplotlib style parameters specified by 'params' dictionary. +params['text'] should contain the message to be drawn.

+ + +

Parameters:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
+ ax + + Axes + +
+

Matplotlib axis object.

+
+
+ required +
+ params + + Dict[str, Any] + +
+

Matplotlib style parameters for drawing text. params['text'] should contain the message to be drawn.

+
+
+ required +
+ background + + BaseGeometry + +
+

Background layer.

+
+
+ required +
+ + +
+ Source code in prettymaps/draw.py +
905
+906
+907
+908
+909
+910
+911
+912
+913
+914
+915
+916
+917
+918
+919
+920
+921
+922
+923
+924
+925
+926
+927
+928
+929
+930
+931
+932
+933
+934
+935
+936
+937
+938
+939
+940
+941
+942
+943
def draw_text(
+    ax: matplotlib.axes.Axes, params: Dict[str, Any], background: BaseGeometry
+) -> None:
+    """
+    Draw text with content and matplotlib style parameters specified by 'params' dictionary.
+    params['text'] should contain the message to be drawn.
+
+    Args:
+        ax (matplotlib.axes.Axes): Matplotlib axis object.
+        params (Dict[str, Any]): Matplotlib style parameters for drawing text. params['text'] should contain the message to be drawn.
+        background (BaseGeometry): Background layer.
+    """
+    # Override default osm_credit dict with provided parameters
+    params = override_params(
+        dict(
+            text="\n".join(
+                [
+                    "data © OpenStreetMap contributors",
+                    "github.com/marceloprates/prettymaps",
+                ]
+            ),
+            x=0,
+            y=1,
+            horizontalalignment="left",
+            verticalalignment="top",
+            bbox=dict(boxstyle="square", fc="#fff", ec="#000"),
+            # fontfamily="Ubuntu Mono",
+        ),
+        params,
+    )
+    x, y, text = [params.pop(k) for k in ["x", "y", "text"]]
+
+    # Get background bounds
+    xmin, ymin, xmax, ymax = background.bounds
+
+    x = np.interp([x], [0, 1], [xmin, xmax])[0]
+    y = np.interp([y], [0, 1], [ymin, ymax])[0]
+
+    ax.text(x, y, text, zorder=1000, **params)
+
+
+
+ +
+ +
+ + +

+ gdf_to_shapely(layer, gdf, width=None, point_size=None, line_width=None, **kwargs) + +

+ + +
+ +

Convert a dict of GeoDataFrames to a dict of shapely geometries

+ + +

Parameters:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
+ layer + + str + +
+

Layer name

+
+
+ required +
+ gdf + + GeoDataFrame + +
+

Input GeoDataFrame

+
+
+ required +
+ width + + Optional[Union[dict, float]] + +
+

Street network width. Can be either a dictionary or a float. Defaults to None.

+
+
+ None +
+ point_size + + Optional[float] + +
+

Point geometries (1D) will be dilated by this amount. Defaults to None.

+
+
+ None +
+ line_width + + Optional[float] + +
+

Line geometries (2D) will be dilated by this amount. Defaults to None.

+
+
+ None +
+ + +

Returns:

+ + + + + + + + + + + + + +
Name TypeDescription
GeometryCollection + GeometryCollection + +
+

Output GeoDataFrame

+
+
+ + +
+ Source code in prettymaps/draw.py +
256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
def gdf_to_shapely(
+    layer: str,
+    gdf: gp.GeoDataFrame,
+    width: Optional[Union[dict, float]] = None,
+    point_size: Optional[float] = None,
+    line_width: Optional[float] = None,
+    **kwargs,
+) -> GeometryCollection:
+    """
+    Convert a dict of GeoDataFrames to a dict of shapely geometries
+
+    Args:
+        layer (str): Layer name
+        gdf (gp.GeoDataFrame): Input GeoDataFrame
+        width (Optional[Union[dict, float]], optional): Street network width. Can be either a dictionary or a float. Defaults to None.
+        point_size (Optional[float], optional): Point geometries (1D) will be dilated by this amount. Defaults to None.
+        line_width (Optional[float], optional): Line geometries (2D) will be dilated by this amount. Defaults to None.
+
+    Returns:
+        GeometryCollection: Output GeoDataFrame
+    """
+
+    # Project gdf if applicable
+    if not gdf.empty and gdf.crs is not None:
+        gdf = ox.project_gdf(gdf)
+
+    if layer in ["streets", "railway", "waterway"]:
+        geometries = graph_to_shapely(gdf, width)
+    else:
+        geometries = geometries_to_shapely(
+            gdf, point_size=point_size, line_width=line_width
+        )
+
+    return geometries
+
+
+
+ +
+ +
+ + +

+ geometries_to_shapely(gdf, point_size=None, line_width=None) + +

+ + +
+ +

Convert geometries in GeoDataFrame to shapely format

+ + +

Parameters:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
+ gdf + + GeoDataFrame + +
+

Input GeoDataFrame

+
+
+ required +
+ point_size + + Optional[float] + +
+

Point geometries (1D) will be dilated by this amount. Defaults to None.

+
+
+ None +
+ line_width + + Optional[float] + +
+

Line geometries (2D) will be dilated by this amount. Defaults to None.

+
+
+ None +
+ + +

Returns:

+ + + + + + + + + + + + + +
Name TypeDescription
GeometryCollection + GeometryCollection + +
+

Shapely geometries computed from GeoDataFrame geometries

+
+
+ + +
+ Source code in prettymaps/draw.py +
215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
def geometries_to_shapely(
+    gdf: gp.GeoDataFrame,
+    point_size: Optional[float] = None,
+    line_width: Optional[float] = None,
+) -> GeometryCollection:
+    """
+    Convert geometries in GeoDataFrame to shapely format
+
+    Args:
+        gdf (gp.GeoDataFrame): Input GeoDataFrame
+        point_size (Optional[float], optional): Point geometries (1D) will be dilated by this amount. Defaults to None.
+        line_width (Optional[float], optional): Line geometries (2D) will be dilated by this amount. Defaults to None.
+
+    Returns:
+        GeometryCollection: Shapely geometries computed from GeoDataFrame geometries
+    """
+
+    geoms = gdf.geometry.tolist()
+    collections = [x for x in geoms if type(x) == GeometryCollection]
+    points = [x for x in geoms if type(x) == Point] + [
+        y for x in collections for y in x.geoms if type(y) == Point
+    ]
+    lines = [x for x in geoms if type(x) in [LineString, MultiLineString]] + [
+        y
+        for x in collections
+        for y in x.geoms
+        if type(y) in [LineString, MultiLineString]
+    ]
+    polys = [x for x in geoms if type(x) in [Polygon, MultiPolygon]] + [
+        y for x in collections for y in x.geoms if type(y) in [Polygon, MultiPolygon]
+    ]
+
+    # Convert points into circles with radius "point_size"
+    if point_size:
+        points = [x.buffer(point_size) for x in points] if point_size > 0 else []
+    if line_width:
+        lines = [x.buffer(line_width) for x in lines] if line_width > 0 else []
+
+    return GeometryCollection(list(points) + list(lines) + list(polys))
+
+
+
+ +
+ +
+ + +

+ graph_to_shapely(gdf, width=1.0) + +

+ + +
+ +

Given a GeoDataFrame containing a graph (street newtork), +convert them to shapely geometries by applying dilation given by 'width'

+ + +

Parameters:

+ + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
+ gdf + + GeoDataFrame + +
+

input GeoDataFrame containing graph (street network) geometries

+
+
+ required +
+ width + + float + +
+

Line geometries will be dilated by this amount. Defaults to 1..

+
+
+ 1.0 +
+ + +

Returns:

+ + + + + + + + + + + + + +
Name TypeDescription
BaseGeometry + BaseGeometry + +
+

Shapely

+
+
+ + +
+ Source code in prettymaps/draw.py +
171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
def graph_to_shapely(gdf: gp.GeoDataFrame, width: float = 1.0) -> BaseGeometry:
+    """
+    Given a GeoDataFrame containing a graph (street newtork),
+    convert them to shapely geometries by applying dilation given by 'width'
+
+    Args:
+        gdf (gp.GeoDataFrame): input GeoDataFrame containing graph (street network) geometries
+        width (float, optional): Line geometries will be dilated by this amount. Defaults to 1..
+
+    Returns:
+        BaseGeometry: Shapely
+    """
+
+    def highway_to_width(highway):
+        if (type(highway) == str) and (highway in width):
+            return width[highway]
+        elif isinstance(highway, Iterable):
+            for h in highway:
+                if h in width:
+                    return width[h]
+            return np.nan
+        else:
+            return np.nan
+
+    # Annotate GeoDataFrame with the width for each highway type
+    gdf["width"] = (
+        gdf["highway"].map(highway_to_width) if type(width) == dict else width
+    )
+
+    # Remove rows with inexistent width
+    gdf.drop(gdf[gdf.width.isna()].index, inplace=True)
+
+    with warnings.catch_warnings():
+        # Supress shapely.errors.ShapelyDeprecationWarning
+        warnings.simplefilter("ignore", shapely.errors.ShapelyDeprecationWarning)
+        if not all(gdf.width.isna()):
+            # Dilate geometries based on their width
+            gdf.geometry = gdf.apply(
+                lambda row: row["geometry"].buffer(row.width), axis=1
+            )
+
+    return shapely.ops.unary_union(gdf.geometry)
+
+
+
+ +
+ +
+ + +

+ manage_presets(load_preset, save_preset, update_preset, layers, style, circle, radius, dilate, logging=False) + +

+ + +
+ +

summary

+ + +

Parameters:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
+ load_preset + + Optional[str] + +
+

Load preset named 'load_preset', if provided

+
+
+ required +
+ save_preset + + Optional[str] + +
+

Save preset to file named 'save_preset', if provided

+
+
+ required +
+ update_preset + + Optional[str] + +
+

Load, update and save preset named 'update_preset', if provided

+
+
+ required +
+ layers + + Dict[str, dict] + +
+

prettymaps.plot() 'layers' parameter dict

+
+
+ required +
+ style + + Dict[str, dict] + +
+

prettymaps.plot() 'style' parameter dict

+
+
+ required +
+ circle + + Optional[bool] + +
+

prettymaps.plot() 'circle' parameter

+
+
+ required +
+ radius + + Optional[Union[float, bool]] + +
+

prettymaps.plot() 'radius' parameter

+
+
+ required +
+ dilate + + Optional[Union[float, bool]] + +
+

prettymaps.plot() 'dilate' parameter

+
+
+ required +
+ + +

Returns:

+ + + + + + + + + + + + + +
TypeDescription
+ Tuple[dict, dict, Optional[float], Optional[Union[float, bool]], Optional[Union[float, bool]]] + +
+

Tuple[dict, dict, Optional[float], Optional[Union[float, bool]], Optional[Union[float, bool]]]: Updated layers, style, circle, radius, dilate parameters

+
+
+ + +
+ Source code in prettymaps/draw.py +
765
+766
+767
+768
+769
+770
+771
+772
+773
+774
+775
+776
+777
+778
+779
+780
+781
+782
+783
+784
+785
+786
+787
+788
+789
+790
+791
+792
+793
+794
+795
+796
+797
+798
+799
+800
+801
+802
+803
+804
+805
+806
+807
+808
+809
+810
+811
+812
+813
+814
+815
+816
+817
+818
+819
+820
+821
@log_execution_time
+def manage_presets(
+    load_preset: Optional[str],
+    save_preset: bool,
+    update_preset: Optional[str],
+    layers: Dict[str, dict],
+    style: Dict[str, dict],
+    circle: Optional[bool],
+    radius: Optional[Union[float, bool]],
+    dilate: Optional[Union[float, bool]],
+    logging=False,
+) -> Tuple[
+    dict,
+    dict,
+    Optional[float],
+    Optional[Union[float, bool]],
+    Optional[Union[float, bool]],
+]:
+    """_summary_
+
+    Args:
+        load_preset (Optional[str]): Load preset named 'load_preset', if provided
+        save_preset (Optional[str]): Save preset to file named 'save_preset', if provided
+        update_preset (Optional[str]): Load, update and save preset named 'update_preset', if provided
+        layers (Dict[str, dict]): prettymaps.plot() 'layers' parameter dict
+        style (Dict[str, dict]): prettymaps.plot() 'style' parameter dict
+        circle (Optional[bool]): prettymaps.plot() 'circle' parameter
+        radius (Optional[Union[float, bool]]): prettymaps.plot() 'radius' parameter
+        dilate (Optional[Union[float, bool]]): prettymaps.plot() 'dilate' parameter
+
+    Returns:
+        Tuple[dict, dict, Optional[float], Optional[Union[float, bool]], Optional[Union[float, bool]]]: Updated layers, style, circle, radius, dilate parameters
+    """
+
+    # Update preset mode: load a preset, update it with additional parameters and update the JSON file
+    if update_preset is not None:
+        # load_preset = save_preset = True
+        load_preset = save_preset = update_preset
+
+    # Load preset (if provided)
+    if load_preset is not None:
+        layers, style, circle, radius, dilate = override_preset(
+            load_preset, layers, style, circle, radius, dilate
+        )
+
+    # Save parameters as preset
+    if save_preset is not None:
+        create_preset(
+            save_preset,
+            layers=layers,
+            style=style,
+            circle=circle,
+            radius=radius,
+            dilate=dilate,
+        )
+
+    return layers, style, circle, radius, dilate
+
+
+
+ +
+ +
+ + +

+ multiplot(*subplots, figsize=None, credit={}, **kwargs) + +

+ + +
+ +

Creates a multiplot using the provided subplots and optional parameters.

+
Parameters:
+

subplots : list + A list of subplot objects to be plotted. +figsize : tuple, optional + A tuple specifying the figure size (width, height) in inches. +credit : dict, optional + A dictionary containing credit information for the plot. +*kwargs : dict, optional + Additional keyword arguments to customize the plot.

+
Returns:
+

None

+ + +
+ Source code in prettymaps/draw.py +
1253
+1254
+1255
+1256
+1257
+1258
+1259
+1260
+1261
+1262
+1263
+1264
+1265
+1266
+1267
+1268
+1269
+1270
+1271
+1272
+1273
+1274
+1275
+1276
+1277
+1278
+1279
+1280
+1281
+1282
+1283
+1284
+1285
+1286
+1287
+1288
+1289
+1290
+1291
+1292
+1293
+1294
+1295
+1296
+1297
+1298
+1299
def multiplot(*subplots, figsize=None, credit={}, **kwargs):
+    """
+    Creates a multiplot using the provided subplots and optional parameters.
+
+    Parameters:
+    -----------
+    *subplots : list
+        A list of subplot objects to be plotted.
+    figsize : tuple, optional
+        A tuple specifying the figure size (width, height) in inches.
+    credit : dict, optional
+        A dictionary containing credit information for the plot.
+    **kwargs : dict, optional
+        Additional keyword arguments to customize the plot.
+
+    Returns:
+    --------
+    None
+    """
+
+    fig = plt.figure(figsize=figsize)
+    ax = plt.subplot(111, aspect="equal")
+
+    mode = "plotter" if "plotter" in kwargs and kwargs["plotter"] else "matplotlib"
+
+    subplots_results = [
+        plot(
+            subplot.query,
+            ax=ax,
+            multiplot=True,
+            **override_params(
+                subplot.kwargs,
+                {
+                    k: v
+                    for k, v in kwargs.items()
+                    if k != "load_preset" or "load_preset" not in subplot.kwargs
+                },
+            ),
+            show=False,
+        )
+        for subplot in subplots
+    ]
+
+    if mode == "matplotlib":
+        ax.axis("off")
+        ax.axis("equal")
+        ax.autoscale()
+
+
+
+ +
+ +
+ + +

+ override_args(layers, circle, dilate, logging=False) + +

+ + +
+ +

Override arguments in layers' kwargs

+ + +

Parameters:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
+ layers + + dict + +
+

prettymaps.plot() Layers parameters dict

+
+
+ required +
+ circle + + Optional[bool] + +
+

prettymaps.plot() 'Circle' parameter

+
+
+ required +
+ dilate + + Optional[Union[float, bool]] + +
+

prettymaps.plot() 'dilate' parameter

+
+
+ required +
+ + +

Returns:

+ + + + + + + + + + + + + +
Name TypeDescription
dict + dict + +
+

output dict

+
+
+ + +
+ Source code in prettymaps/draw.py +
1041
+1042
+1043
+1044
+1045
+1046
+1047
+1048
+1049
+1050
+1051
+1052
+1053
+1054
+1055
+1056
+1057
+1058
+1059
+1060
+1061
+1062
+1063
+1064
@log_execution_time
+def override_args(
+    layers: dict,
+    circle: Optional[bool],
+    dilate: Optional[Union[float, bool]],
+    logging=False,
+) -> dict:
+    """
+    Override arguments in layers' kwargs
+
+    Args:
+        layers (dict): prettymaps.plot() Layers parameters dict
+        circle (Optional[bool]): prettymaps.plot() 'Circle' parameter
+        dilate (Optional[Union[float, bool]]): prettymaps.plot() 'dilate' parameter
+
+    Returns:
+        dict: output dict
+    """
+    override_args = ["circle", "dilate"]
+    for layer in layers:
+        for arg in override_args:
+            if arg not in layers[layer]:
+                layers[layer][arg] = locals()[arg]
+    return layers
+
+
+
+ +
+ +
+ + +

+ override_params(default_dict, new_dict) + +

+ + +
+ +

Override parameters in 'default_dict' with additional parameters from 'new_dict'

+ + +

Parameters:

+ + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
+ default_dict + + dict + +
+

Default dict to be overriden with 'new_dict' parameters

+
+
+ required +
+ new_dict + + dict + +
+

New dict to override 'default_dict' parameters

+
+
+ required +
+ + +

Returns:

+ + + + + + + + + + + + + +
Name TypeDescription
dict + dict + +
+

default_dict overriden with new_dict parameters

+
+
+ + +
+ Source code in prettymaps/draw.py +
1015
+1016
+1017
+1018
+1019
+1020
+1021
+1022
+1023
+1024
+1025
+1026
+1027
+1028
+1029
+1030
+1031
+1032
+1033
+1034
+1035
+1036
+1037
+1038
def override_params(default_dict: dict, new_dict: dict) -> dict:
+    """
+    Override parameters in 'default_dict' with additional parameters from 'new_dict'
+
+    Args:
+        default_dict (dict): Default dict to be overriden with 'new_dict' parameters
+        new_dict (dict): New dict to override 'default_dict' parameters
+
+    Returns:
+        dict: default_dict overriden with new_dict parameters
+    """
+
+    final_dict = deepcopy(default_dict)
+
+    for key in new_dict.keys():
+        if type(new_dict[key]) == dict:
+            if key in final_dict:
+                final_dict[key] = override_params(final_dict[key], new_dict[key])
+            else:
+                final_dict[key] = new_dict[key]
+        else:
+            final_dict[key] = new_dict[key]
+
+    return final_dict
+
+
+
+ +
+ +
+ + +

+ override_preset(name, layers={}, style={}, circle=None, radius=None, dilate=None) + +

+ + +
+ +

Read the preset file given by 'name' and override it with additional parameters

+ + +

Parameters:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
+ name + + str + +
+

description

+
+
+ required +
+ layers + + Dict[str, dict] + +
+

description. Defaults to {}.

+
+
+ {} +
+ style + + Dict[str, dict] + +
+

description. Defaults to {}.

+
+
+ {} +
+ circle + + Union[float, None] + +
+

description. Defaults to None.

+
+
+ None +
+ radius + + Union[float, None] + +
+

description. Defaults to None.

+
+
+ None +
+ dilate + + Union[float, None] + +
+

description. Defaults to None.

+
+
+ None +
+ + +

Returns:

+ + + + + + + + + + + + + +
TypeDescription
+ Tuple[dict, dict, Optional[float], Optional[Union[float, bool]], Optional[Union[float, bool]]] + +
+

Tuple[dict, dict, Optional[float], Optional[Union[float, bool]], Optional[Union[float, bool]]]: Preset parameters overriden by additional provided parameters

+
+
+ + +
+ Source code in prettymaps/draw.py +
849
+850
+851
+852
+853
+854
+855
+856
+857
+858
+859
+860
+861
+862
+863
+864
+865
+866
+867
+868
+869
+870
+871
+872
+873
+874
+875
+876
+877
+878
+879
+880
+881
+882
+883
+884
+885
+886
+887
+888
+889
+890
+891
+892
+893
+894
+895
+896
+897
def override_preset(
+    name: str,
+    layers: Dict[str, dict] = {},
+    style: Dict[str, dict] = {},
+    circle: Optional[float] = None,
+    radius: Optional[Union[float, bool]] = None,
+    dilate: Optional[Union[float, bool]] = None,
+) -> Tuple[
+    dict,
+    dict,
+    Optional[float],
+    Optional[Union[float, bool]],
+    Optional[Union[float, bool]],
+]:
+    """
+    Read the preset file given by 'name' and override it with additional parameters
+
+    Args:
+        name (str): _description_
+        layers (Dict[str, dict], optional): _description_. Defaults to {}.
+        style (Dict[str, dict], optional): _description_. Defaults to {}.
+        circle (Union[float, None], optional): _description_. Defaults to None.
+        radius (Union[float, None], optional): _description_. Defaults to None.
+        dilate (Union[float, None], optional): _description_. Defaults to None.
+
+    Returns:
+        Tuple[dict, dict, Optional[float], Optional[Union[float, bool]], Optional[Union[float, bool]]]: Preset parameters overriden by additional provided parameters
+    """
+
+    params = read_preset(name)
+
+    # Override preset with kwargs
+    if "layers" in params:
+        layers = override_params(params["layers"], layers)
+    if "style" in params:
+        style = override_params(params["style"], style)
+    if circle is None and "circle" in params:
+        circle = params["circle"]
+    if radius is None and "radius" in params:
+        radius = params["radius"]
+    if dilate is None and "dilate" in params:
+        dilate = params["dilate"]
+
+    # Delete layers marked as 'False' in the parameter dict
+    for layer in [key for key in layers.keys() if layers[key] == False]:
+        del layers[layer]
+
+    # Return overriden presets
+    return layers, style, circle, radius, dilate
+
+
+
+ +
+ +
+ + +

+ plot(query, layers={}, style={}, keypoints={}, preset='default', use_preset=True, save_preset=None, update_preset=None, postprocessing=lambda x: x, circle=None, radius=None, dilate=None, save_as=None, fig=None, ax=None, figsize=(11.7, 11.7), credit={}, mode='matplotlib', multiplot=False, show=True, x=0, y=0, scale_x=1, scale_y=1, rotation=0, logging=False, semantic=False, adjust_aspect_ratio=True) + +

+ + +
+ +

Plots a map based on a given query and specified parameters. +Args: + query: The query for the location to plot. This can be a string (e.g., "Porto Alegre"), a tuple of latitude and longitude coordinates, or a custom GeoDataFrame boundary. + layers: The OpenStreetMap layers to plot. Defaults to an empty dictionary. + style: Matplotlib parameters for drawing each layer. Defaults to an empty dictionary. + keypoints: Keypoints to highlight on the map. Defaults to an empty dictionary. + preset: Preset configuration to use. Defaults to "default". + use_preset: Whether to use the preset configuration. Defaults to True. + save_preset: Path to save the preset configuration. Defaults to None. + update_preset: Path to update the preset configuration with additional parameters. Defaults to None. + circle: Whether to use a circular boundary. Defaults to None. + radius: Radius for the circular or square boundary. Defaults to None. + dilate: Amount to dilate the boundary. Defaults to None. + save_as: Path to save the resulting plot. Defaults to None. + fig: Matplotlib figure object. Defaults to None. + ax: Matplotlib axes object. Defaults to None. + title: Title of the plot. Defaults to None. + figsize: Size of the figure. Defaults to (11.7, 11.7). + constrained_layout: Whether to use constrained layout for the figure. Defaults to True. + credit: Parameters for the credit message. Defaults to an empty dictionary. + mode: Mode for plotting ('matplotlib' or 'plotter'). Defaults to "matplotlib". + multiplot: Whether to use multiplot mode. Defaults to False. + show: Whether to display the plot using matplotlib. Defaults to True. + x: Translation parameter in the x direction. Defaults to 0. + y: Translation parameter in the y direction. Defaults to 0. + scale_x: Scaling parameter in the x direction. Defaults to 1. + scale_y: Scaling parameter in the y direction. Defaults to 1. + rotation: Rotation parameter in degrees. Defaults to 0. + logging: Whether to enable logging. Defaults to False. +Returns: + Plot: The resulting plot object.

+ + +
+ Source code in prettymaps/draw.py +
1102
+1103
+1104
+1105
+1106
+1107
+1108
+1109
+1110
+1111
+1112
+1113
+1114
+1115
+1116
+1117
+1118
+1119
+1120
+1121
+1122
+1123
+1124
+1125
+1126
+1127
+1128
+1129
+1130
+1131
+1132
+1133
+1134
+1135
+1136
+1137
+1138
+1139
+1140
+1141
+1142
+1143
+1144
+1145
+1146
+1147
+1148
+1149
+1150
+1151
+1152
+1153
+1154
+1155
+1156
+1157
+1158
+1159
+1160
+1161
+1162
+1163
+1164
+1165
+1166
+1167
+1168
+1169
+1170
+1171
+1172
+1173
+1174
+1175
+1176
+1177
+1178
+1179
+1180
+1181
+1182
+1183
+1184
+1185
+1186
+1187
+1188
+1189
+1190
+1191
+1192
+1193
+1194
+1195
+1196
+1197
+1198
+1199
+1200
+1201
+1202
+1203
+1204
+1205
+1206
+1207
+1208
+1209
+1210
+1211
+1212
+1213
+1214
+1215
+1216
+1217
+1218
+1219
+1220
+1221
+1222
+1223
+1224
+1225
+1226
+1227
+1228
+1229
+1230
+1231
+1232
+1233
+1234
+1235
+1236
+1237
+1238
+1239
+1240
+1241
+1242
+1243
+1244
+1245
+1246
+1247
+1248
+1249
+1250
def plot(
+    query: str | Tuple[float, float] | gp.GeoDataFrame,
+    layers: Dict[str, Dict[str, Any]] = {},
+    style: Dict[str, Dict[str, Any]] = {},
+    keypoints: Dict[str, Any] = {},
+    preset: str = "default",
+    use_preset: bool = True,
+    save_preset: str | None = None,
+    update_preset: str | None = None,
+    postprocessing: Callable[
+        [Dict[str, gp.GeoDataFrame]], Dict[str, gp.GeoDataFrame]
+    ] = lambda x: x,
+    circle: bool | None = None,
+    radius: float | bool | None = None,
+    dilate: float | bool | None = None,
+    save_as: str | None = None,
+    fig: plt.Figure | None = None,
+    ax: plt.Axes | None = None,
+    figsize: Tuple[float, float] = (11.7, 11.7),
+    credit: Dict[str, Any] = {},
+    mode: str = "matplotlib",
+    multiplot: bool = False,
+    show: bool = True,
+    x: float = 0,
+    y: float = 0,
+    scale_x: float = 1,
+    scale_y: float = 1,
+    rotation: float = 0,
+    logging: bool = False,
+    semantic: bool = False,
+    adjust_aspect_ratio: bool = True,
+) -> Plot:
+    """
+    Plots a map based on a given query and specified parameters.
+    Args:
+        query: The query for the location to plot. This can be a string (e.g., "Porto Alegre"), a tuple of latitude and longitude coordinates, or a custom GeoDataFrame boundary.
+        layers: The OpenStreetMap layers to plot. Defaults to an empty dictionary.
+        style: Matplotlib parameters for drawing each layer. Defaults to an empty dictionary.
+        keypoints: Keypoints to highlight on the map. Defaults to an empty dictionary.
+        preset: Preset configuration to use. Defaults to "default".
+        use_preset: Whether to use the preset configuration. Defaults to True.
+        save_preset: Path to save the preset configuration. Defaults to None.
+        update_preset: Path to update the preset configuration with additional parameters. Defaults to None.
+        circle: Whether to use a circular boundary. Defaults to None.
+        radius: Radius for the circular or square boundary. Defaults to None.
+        dilate: Amount to dilate the boundary. Defaults to None.
+        save_as: Path to save the resulting plot. Defaults to None.
+        fig: Matplotlib figure object. Defaults to None.
+        ax: Matplotlib axes object. Defaults to None.
+        title: Title of the plot. Defaults to None.
+        figsize: Size of the figure. Defaults to (11.7, 11.7).
+        constrained_layout: Whether to use constrained layout for the figure. Defaults to True.
+        credit: Parameters for the credit message. Defaults to an empty dictionary.
+        mode: Mode for plotting ('matplotlib' or 'plotter'). Defaults to "matplotlib".
+        multiplot: Whether to use multiplot mode. Defaults to False.
+        show: Whether to display the plot using matplotlib. Defaults to True.
+        x: Translation parameter in the x direction. Defaults to 0.
+        y: Translation parameter in the y direction. Defaults to 0.
+        scale_x: Scaling parameter in the x direction. Defaults to 1.
+        scale_y: Scaling parameter in the y direction. Defaults to 1.
+        rotation: Rotation parameter in degrees. Defaults to 0.
+        logging: Whether to enable logging. Defaults to False.
+    Returns:
+        Plot: The resulting plot object.
+    """
+
+    # 1. Manage presets
+    layers, style, circle, radius, dilate = manage_presets(
+        preset if use_preset else None,
+        save_preset,
+        update_preset,
+        layers,
+        style,
+        circle,
+        radius,
+        dilate,
+    )
+
+    # 2. Init matplotlib figure & axis and vsketch object
+    fig, ax, vsk = init_plot(
+        layers,
+        fig,
+        ax,
+        figsize,
+        mode,
+        adjust_aspect_ratio=adjust_aspect_ratio,
+        logging=logging,
+    )
+
+    # 3. Override arguments in layers' kwargs dict
+    layers = override_args(layers, circle, dilate, logging=logging)
+
+    # 4. Fetch geodataframes
+    start_time = time.time()
+    gdfs = get_gdfs(query, layers, radius, dilate, -rotation, logging=logging)
+    fetch_time = time.time() - start_time
+    print(f"Fetching geodataframes took {fetch_time:.2f} seconds")
+
+    # 5. Apply transformations to GeoDataFrames (translation, scale, rotation)
+    gdfs = transform_gdfs(gdfs, x, y, scale_x, scale_y, rotation, logging=logging)
+
+    # 6. Apply a postprocessing function to the GeoDataFrames, if provided
+    gdfs = postprocessing(gdfs)
+
+    # 7. Create background GeoDataFrame and get (x,y) bounds
+    background, xmin, ymin, xmax, ymax, dx, dy = create_background(
+        gdfs, style, logging=logging
+    )
+
+    # 8. Draw layers
+    draw_layers(layers, gdfs, style, fig, ax, vsk, mode, logging=logging)
+
+    # 9. Draw keypoints
+    keypoints = draw_keypoints(keypoints, gdfs, ax, logging=logging)
+
+    # 9. Draw background
+    draw_background(background, ax, style, mode, logging=logging)
+
+    # 10. Draw credit message
+    draw_credit(ax, background, credit, mode, multiplot, logging=logging)
+
+    # 11. Draw hillshade
+    draw_hillshade(
+        layers,
+        gdfs,
+        ax,
+        logging=logging,
+        **(layers["hillshade"] if "hillshade" in layers else {}),
+    )
+
+    # 12. Create Plot object
+    plot = Plot(gdfs, fig, ax, background, keypoints)
+
+    # 13. Save plot as image
+    if save_as is not None:
+        plt.savefig(save_as)
+
+    # 14. Show plot
+    if show:
+        if mode == "plotter":
+            vsk.display()
+        elif mode == "matplotlib":
+            plt.show()
+        else:
+            raise Exception(f"Unknown mode {mode}")
+    else:
+        plt.close()
+
+    return plot
+
+
+
+ +
+ +
+ + +

+ plot_gdf(layer, gdf, ax, mode='matplotlib', vsk=None, palette=None, width=None, union=False, dilate_points=None, dilate_lines=None, **kwargs) + +

+ + +
+ +

Plot a layer

+ + +

Parameters:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
+ layer + + str + +
+

layer name

+
+
+ required +
+ gdf + + GeoDataFrame + +
+

GeoDataFrame

+
+
+ required +
+ ax + + Axes + +
+

matplotlib axis object

+
+
+ required +
+ mode + + str + +
+

drawing mode. Options: 'matplotlib', 'vsketch'. Defaults to 'matplotlib'

+
+
+ 'matplotlib' +
+ vsk + + Optional[SketchClass] + +
+

Vsketch object. Mandatory if mode == 'plotter'

+
+
+ None +
+ palette + + Optional[List[str]] + +
+

Color palette. Defaults to None.

+
+
+ None +
+ width + + Optional[Union[dict, float]] + +
+

Street widths. Either a dictionary or a float. Defaults to None.

+
+
+ None +
+ union + + bool + +
+

Whether to join geometries. Defaults to False.

+
+
+ False +
+ dilate_points + + Optional[float] + +
+

Amount of dilation to be applied to point (1D) geometries. Defaults to None.

+
+
+ None +
+ dilate_lines + + Optional[float] + +
+

Amount of dilation to be applied to line (2D) geometries. Defaults to None.

+
+
+ None +
+ + +

Raises:

+ + + + + + + + + + + + + +
TypeDescription
+ Exception + +
+

description

+
+
+ + +
+ Source code in prettymaps/draw.py +
292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+338
+339
+340
+341
+342
+343
+344
+345
+346
+347
+348
+349
+350
+351
+352
+353
+354
+355
+356
+357
+358
+359
+360
+361
+362
+363
+364
+365
+366
+367
+368
+369
+370
+371
+372
+373
+374
+375
+376
+377
+378
+379
+380
+381
+382
+383
+384
+385
+386
+387
+388
+389
+390
+391
+392
+393
+394
+395
+396
+397
+398
+399
+400
+401
+402
+403
+404
+405
+406
+407
+408
+409
+410
+411
+412
+413
+414
+415
+416
+417
+418
+419
+420
def plot_gdf(
+    layer: str,
+    gdf: gp.GeoDataFrame,
+    ax: matplotlib.axes.Axes,
+    mode: str = "matplotlib",
+    # vsk: Optional[vsketch.SketchClass] = None,
+    vsk=None,
+    palette: Optional[List[str]] = None,
+    width: Optional[Union[dict, float]] = None,
+    union: bool = False,
+    dilate_points: Optional[float] = None,
+    dilate_lines: Optional[float] = None,
+    **kwargs,
+) -> None:
+    """
+    Plot a layer
+
+    Args:
+        layer (str): layer name
+        gdf (gp.GeoDataFrame): GeoDataFrame
+        ax (matplotlib.axes.Axes): matplotlib axis object
+        mode (str): drawing mode. Options: 'matplotlib', 'vsketch'. Defaults to 'matplotlib'
+        vsk (Optional[vsketch.SketchClass]): Vsketch object. Mandatory if mode == 'plotter'
+        palette (Optional[List[str]], optional): Color palette. Defaults to None.
+        width (Optional[Union[dict, float]], optional): Street widths. Either a dictionary or a float. Defaults to None.
+        union (bool, optional): Whether to join geometries. Defaults to False.
+        dilate_points (Optional[float], optional): Amount of dilation to be applied to point (1D) geometries. Defaults to None.
+        dilate_lines (Optional[float], optional): Amount of dilation to be applied to line (2D) geometries. Defaults to None.
+
+    Raises:
+        Exception: _description_
+    """
+
+    # Get hatch and hatch_c parameter
+    hatch_c = kwargs.pop("hatch_c") if "hatch_c" in kwargs else None
+
+    # Convert GDF to shapely geometries
+    geometries = gdf_to_shapely(
+        layer, gdf, width, point_size=dilate_points, line_width=dilate_lines
+    )
+
+    # Unite geometries
+    if union:
+        geometries = shapely.ops.unary_union(GeometryCollection([geometries]))
+
+    if (palette is None) and ("fc" in kwargs) and (type(kwargs["fc"]) != str):
+        palette = kwargs.pop("fc")
+
+    for shape in geometries.geoms if hasattr(geometries, "geoms") else [geometries]:
+        if mode == "matplotlib":
+            if type(shape) in [Polygon, MultiPolygon]:
+                # Plot main shape (without silhouette)
+                ax.add_patch(
+                    PolygonPatch(
+                        shape,
+                        lw=0,
+                        ec=(
+                            hatch_c
+                            if hatch_c
+                            else kwargs["ec"] if "ec" in kwargs else None
+                        ),
+                        fc=(
+                            kwargs["fc"]
+                            if "fc" in kwargs
+                            else np.random.choice(palette) if palette else None
+                        ),
+                        **{
+                            k: v
+                            for k, v in kwargs.items()
+                            if k not in ["lw", "ec", "fc"]
+                        },
+                    ),
+                )
+                # Plot just silhouette
+                ax.add_patch(
+                    PolygonPatch(
+                        shape,
+                        fill=False,
+                        **{
+                            k: v
+                            for k, v in kwargs.items()
+                            if k not in ["hatch", "fill"]
+                        },
+                    )
+                )
+            elif type(shape) == LineString:
+                ax.plot(
+                    *shape.xy,
+                    c=kwargs["ec"] if "ec" in kwargs else None,
+                    **{
+                        k: v
+                        for k, v in kwargs.items()
+                        if k in ["lw", "ls", "dashes", "zorder"]
+                    },
+                )
+            elif type(shape) == MultiLineString:
+                for c in shape.geoms:
+                    ax.plot(
+                        *c.xy,
+                        c=kwargs["ec"] if "ec" in kwargs else None,
+                        **{
+                            k: v
+                            for k, v in kwargs.items()
+                            if k in ["lw", "lt", "dashes", "zorder"]
+                        },
+                    )
+        elif mode == "plotter":
+            if ("draw" not in kwargs) or kwargs["draw"]:
+
+                # Set stroke
+                if "stroke" in kwargs:
+                    vsk.stroke(kwargs["stroke"])
+                else:
+                    vsk.stroke(1)
+
+                # Set pen width
+                if "penWidth" in kwargs:
+                    vsk.penWidth(kwargs["penWidth"])
+                else:
+                    vsk.penWidth(0.3)
+
+                if "fill" in kwargs:
+                    vsk.fill(kwargs["fill"])
+                else:
+                    vsk.noFill()
+
+                vsk.geometry(shape)
+        else:
+            raise Exception(f"Unknown mode {mode}")
+
+
+
+ +
+ +
+ + +

+ presets_directory() + +

+ + +
+ +

Returns the path to the 'presets' directory. +This function constructs the path to the 'presets' directory, which is +located in the same directory as the current script file. +Returns: + str: The full path to the 'presets' directory.

+ + +
+ Source code in prettymaps/draw.py +
698
+699
+700
+701
+702
+703
+704
+705
+706
+707
def presets_directory():
+    """
+    Returns the path to the 'presets' directory.
+    This function constructs the path to the 'presets' directory, which is
+    located in the same directory as the current script file.
+    Returns:
+        str: The full path to the 'presets' directory.
+    """
+
+    return os.path.join(pathlib.Path(__file__).resolve().parent, "presets")
+
+
+
+ +
+ +
+ + +

+ read_preset(name) + +

+ + +
+ +

Read a preset from the presets folder (prettymaps/presets/)

+ + +

Parameters:

+ + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
+ name + + str + +
+

Preset name

+
+
+ required +
+ + +

Returns:

+ + + + + + + + + + + + + +
TypeDescription
+ Dict[str, dict] + +
+

parameters dictionary

+
+
+ + +
+ Source code in prettymaps/draw.py +
748
+749
+750
+751
+752
+753
+754
+755
+756
+757
+758
+759
+760
+761
+762
def read_preset(name: str) -> Dict[str, dict]:
+    """
+    Read a preset from the presets folder (prettymaps/presets/)
+
+    Args:
+        name (str): Preset name
+
+    Returns:
+        (Dict[str,dict]): parameters dictionary
+    """
+    path = os.path.join(presets_directory(), f"{name}.json")
+    with open(path, "r") as f:
+        # Load params from JSON file
+        params = json.load(f)
+    return params
+
+
+
+ +
+ +
+ + +

+ transform_gdfs(gdfs, x=0, y=0, scale_x=1, scale_y=1, rotation=0, logging=False) + +

+ + +
+ +

Apply geometric transformations to dictionary of GeoDataFrames

+ + +

Parameters:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
+ gdfs + + Dict[str, GeoDataFrame] + +
+

Dictionary of GeoDataFrames

+
+
+ required +
+ x + + float + +
+

x-axis translation. Defaults to 0.

+
+
+ 0 +
+ y + + float + +
+

y-axis translation. Defaults to 0.

+
+
+ 0 +
+ scale_x + + float + +
+

x-axis scale. Defaults to 1.

+
+
+ 1 +
+ scale_y + + float + +
+

y-axis scale. Defaults to 1.

+
+
+ 1 +
+ rotation + + float + +
+

rotation angle (in radians). Defaults to 0.

+
+
+ 0 +
+ + +

Returns:

+ + + + + + + + + + + + + +
TypeDescription
+ Dict[str, GeoDataFrame] + +
+

Dict[str, gp.GeoDataFrame]: dictionary of transformed GeoDataFrames

+
+
+ + +
+ Source code in prettymaps/draw.py +
647
+648
+649
+650
+651
+652
+653
+654
+655
+656
+657
+658
+659
+660
+661
+662
+663
+664
+665
+666
+667
+668
+669
+670
+671
+672
+673
+674
+675
+676
+677
+678
+679
+680
+681
+682
+683
+684
+685
+686
+687
+688
+689
+690
@log_execution_time
+def transform_gdfs(
+    gdfs: Dict[str, gp.GeoDataFrame],
+    x: float = 0,
+    y: float = 0,
+    scale_x: float = 1,
+    scale_y: float = 1,
+    rotation: float = 0,
+    logging=False,
+) -> Dict[str, gp.GeoDataFrame]:
+    """
+    Apply geometric transformations to dictionary of GeoDataFrames
+
+    Args:
+        gdfs (Dict[str, gp.GeoDataFrame]): Dictionary of GeoDataFrames
+        x (float, optional): x-axis translation. Defaults to 0.
+        y (float, optional): y-axis translation. Defaults to 0.
+        scale_x (float, optional): x-axis scale. Defaults to 1.
+        scale_y (float, optional): y-axis scale. Defaults to 1.
+        rotation (float, optional): rotation angle (in radians). Defaults to 0.
+
+    Returns:
+        Dict[str, gp.GeoDataFrame]: dictionary of transformed GeoDataFrames
+    """
+    # Project geometries
+    gdfs = {
+        name: ox.project_gdf(gdf) if len(gdf) > 0 else gdf for name, gdf in gdfs.items()
+    }
+    # Create geometry collection from gdfs' geometries
+    collection = GeometryCollection(
+        [GeometryCollection(list(gdf.geometry)) for gdf in gdfs.values()]
+    )
+    # Translation, scale & rotation
+    collection = shapely.affinity.translate(collection, x, y)
+    collection = shapely.affinity.scale(collection, scale_x, scale_y)
+    collection = shapely.affinity.rotate(collection, rotation)
+    # Update geometries
+    for i, layer in enumerate(gdfs):
+        gdfs[layer].geometry = list(collection.geoms[i].geoms)
+        # Reproject
+        if len(gdfs[layer]) > 0:
+            gdfs[layer] = ox.project_gdf(gdfs[layer], to_crs="EPSG:4326")
+
+    return gdfs
+
+
+
+ +
+ + + +
+ +
+ +
+ +
+ + + +

+ prettymaps.fetch + + +

+ +
+ +

Prettymaps - A minimal Python library to draw pretty maps from OpenStreetMap Data +Copyright (C) 2021 Marcelo Prates

+

This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published +by the Free Software Foundation, either version 3 of the License, or +(at your option) any later version.

+

This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details.

+

You should have received a copy of the GNU Affero General Public License +along with this program. If not, see https://www.gnu.org/licenses/.

+ + + + + + + + + +
+ + + + + + + + + +
+ + +

+ get_keypoints(perimeter, tags={'place': 'quarter', 'highway': True, 'building': True, 'landuse': True, 'natural': True, 'waterway': True, 'amenity': True, 'leisure': True, 'shop': True, 'public_transport': True, 'tourism': True, 'historic': True, 'barrier': True, 'power': True, 'railway': True, 'cycleway': True, 'footway': True, 'healthcare': True, 'office': True, 'craft': True, 'man_made': True, 'boundary': True}) + +

+ + +
+ +

Extract keypoints from a given perimeter based on specified tags.

+

Parameters: +perimeter (shapely.geometry.Polygon): The polygon representing the area of interest. +tags (dict, optional): A dictionary of tags to filter the keypoints. The keys are tag names and the values are booleans indicating whether to include the tag. Default includes a variety of common tags.

+

Returns: +geopandas.GeoDataFrame: A GeoDataFrame containing the keypoints that match the specified tags within the given perimeter.

+ + +
+ Source code in prettymaps/fetch.py +
 61
+ 62
+ 63
+ 64
+ 65
+ 66
+ 67
+ 68
+ 69
+ 70
+ 71
+ 72
+ 73
+ 74
+ 75
+ 76
+ 77
+ 78
+ 79
+ 80
+ 81
+ 82
+ 83
+ 84
+ 85
+ 86
+ 87
+ 88
+ 89
+ 90
+ 91
+ 92
+ 93
+ 94
+ 95
+ 96
+ 97
+ 98
+ 99
+100
def get_keypoints(
+    perimeter,
+    tags={
+        "place": "quarter",
+        "highway": True,
+        "building": True,
+        "landuse": True,
+        "natural": True,
+        "waterway": True,
+        "amenity": True,
+        "leisure": True,
+        "shop": True,
+        "public_transport": True,
+        "tourism": True,
+        "historic": True,
+        "barrier": True,
+        "power": True,
+        "railway": True,
+        "cycleway": True,
+        "footway": True,
+        "healthcare": True,
+        "office": True,
+        "craft": True,
+        "man_made": True,
+        "boundary": True,
+    },
+):
+    """
+    Extract keypoints from a given perimeter based on specified tags.
+
+    Parameters:
+    perimeter (shapely.geometry.Polygon): The polygon representing the area of interest.
+    tags (dict, optional): A dictionary of tags to filter the keypoints. The keys are tag names and the values are booleans indicating whether to include the tag. Default includes a variety of common tags.
+
+    Returns:
+    geopandas.GeoDataFrame: A GeoDataFrame containing the keypoints that match the specified tags within the given perimeter.
+    """
+    keypoints_df = ox.features_from_polygon(perimeter, tags=tags)
+
+    return keypoints_df
+
+
+
+ +
+ +
+ + +

+ merge_tags(layers_dict) + +

+ + +
+ +

Merge tags from a dictionary of layers into a single dictionary.

+

Parameters: +layers_dict (dict): Dictionary of layers with their respective tags.

+

Returns: +dict: Merged dictionary of tags.

+ + +
+ Source code in prettymaps/fetch.py +
522
+523
+524
+525
+526
+527
+528
+529
+530
+531
+532
+533
+534
+535
+536
+537
+538
+539
+540
+541
+542
+543
+544
+545
+546
+547
+548
+549
+550
+551
+552
+553
+554
+555
+556
+557
+558
+559
+560
+561
+562
+563
+564
+565
+566
+567
+568
+569
def merge_tags(layers_dict: dict) -> dict:
+    """
+    Merge tags from a dictionary of layers into a single dictionary.
+
+    Parameters:
+    layers_dict (dict): Dictionary of layers with their respective tags.
+
+    Returns:
+    dict: Merged dictionary of tags.
+    """
+
+    layers_dict = deepcopy(layers_dict)
+    merged_tags = {}
+
+    def _merge(d: dict):
+        for key, value in d.items():
+            if isinstance(value, dict):
+                if "tags" in value:
+                    for tag_key, tag_value in value["tags"].items():
+                        if tag_key in merged_tags:
+                            if isinstance(merged_tags[tag_key], list):
+                                if isinstance(tag_value, list):
+                                    merged_tags[tag_key].extend(tag_value)
+                                else:
+                                    merged_tags[tag_key].append(tag_value)
+                            else:
+                                merged_tags[tag_key] = (
+                                    [merged_tags[tag_key], tag_value]
+                                    if not isinstance(tag_value, list)
+                                    else [merged_tags[tag_key]] + tag_value
+                                )
+                        else:
+                            merged_tags[tag_key] = (
+                                tag_value
+                                if isinstance(tag_value, list)
+                                else [tag_value]
+                            )
+                _merge(value)
+
+    _merge(layers_dict)
+
+    # Simplify lists with a single element
+    merged_tags = {
+        k: (v[0] if isinstance(v, list) and len(v) == 1 else v)
+        for k, v in merged_tags.items()
+    }
+
+    return merged_tags
+
+
+
+ +
+ +
+ + +

+ obtain_elevation(gdf) + +

+ + +
+ +

Download all SRTM elevation tiles for the given polygon in a GeoDataFrame.

+

Parameters: +gdf (GeoDataFrame): GeoDataFrame containing the polygon. +output_dir (str): Directory to save the downloaded tiles.

+ + +
+ Source code in prettymaps/fetch.py +
103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
def obtain_elevation(gdf):
+    """
+    Download all SRTM elevation tiles for the given polygon in a GeoDataFrame.
+
+    Parameters:
+    gdf (GeoDataFrame): GeoDataFrame containing the polygon.
+    output_dir (str): Directory to save the downloaded tiles.
+    """
+
+    # Ensure the GeoDataFrame has a single polygon
+    if len(gdf) != 1:
+        raise ValueError("GeoDataFrame must contain a single polygon")
+
+    with warnings.catch_warnings():
+        warnings.simplefilter("ignore")
+        subprocess.run(
+            ["eio", "clean"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL
+        )
+
+    # Get the bounding box of the polygon
+    bounds = gdf.total_bounds
+    min_lon, min_lat, max_lon, max_lat = bounds
+
+    # Configure the bounding box for the elevation library
+
+    output_file = os.path.join(os.getcwd(), "elevation.tif")
+    elevation.clip(
+        bounds=(min_lon, min_lat, max_lon, max_lat),
+        output=output_file,
+        margin="10%",
+        cache_dir=".",
+    )
+
+    # subprocess.run(
+    #    [
+    #        "gdalwarp",
+    #        "-tr",
+    #        "30",
+    #        "30",
+    #        "-r",
+    #        "cubic",
+    #        "elevation.tif",
+    #        "resampled_elevation.tif",
+    #    ],
+    #    # stdout=subprocess.DEVNULL,
+    #    # stderr=subprocess.DEVNULL,
+    # )
+
+    raster = rxr.open_rasterio(output_file).squeeze()
+
+    raster = raster.rio.reproject(CRS.from_string(ox.project_gdf(gdf).crs.to_string()))
+
+    # convert to numpy array
+    elevation_data = raster.data
+
+    return elevation_data
+
+
+
+ +
+ +
+ + +

+ read_from_cache(perimeter, layer_kwargs, cache_dir='prettymaps_cache') + +

+ + +
+ +

Read a GeoDataFrame from the cache based on the perimeter and layer arguments.

+

Parameters: +perimeter (GeoDataFrame): The perimeter GeoDataFrame. +layer_kwargs (dict): Dictionary of layer arguments. +cache_dir (str): Directory to read the cached GeoDataFrame from.

+

Returns: +GeoDataFrame: The cached GeoDataFrame, or None if it does not exist.

+ + +
+ Source code in prettymaps/fetch.py +
348
+349
+350
+351
+352
+353
+354
+355
+356
+357
+358
+359
+360
+361
+362
+363
+364
+365
+366
+367
+368
+369
+370
+371
+372
+373
+374
+375
+376
+377
+378
+379
def read_from_cache(
+    perimeter: GeoDataFrame,
+    layer_kwargs: dict,
+    cache_dir: str = "prettymaps_cache",
+) -> GeoDataFrame:
+    """
+    Read a GeoDataFrame from the cache based on the perimeter and layer arguments.
+
+    Parameters:
+    perimeter (GeoDataFrame): The perimeter GeoDataFrame.
+    layer_kwargs (dict): Dictionary of layer arguments.
+    cache_dir (str): Directory to read the cached GeoDataFrame from.
+
+    Returns:
+    GeoDataFrame: The cached GeoDataFrame, or None if it does not exist.
+    """
+    np.random.seed(0)
+    # Create hash from perimeter
+    perimeter_hash = hash(perimeter["geometry"].to_json())
+    # Create hash from kwargs
+    kwargs_hash = hash(str(layer_kwargs))
+    # Join hashes
+    hash_str = f"{perimeter_hash}_{kwargs_hash}"
+
+    cache_path = os.path.join(cache_dir, f"{hash_str}.geojson")
+
+    # Check if the gdf is cached
+    if os.path.exists(cache_path):
+        # Read cached gdf
+        return gp.read_file(cache_path)
+    else:
+        return None
+
+
+
+ +
+ +
+ + +

+ unified_osm_request(perimeter, layers_dict, logging=False) + +

+ + +
+ +

Unify all OSM requests into one to improve efficiency.

+

Parameters: +perimeter (GeoDataFrame): The perimeter GeoDataFrame. +layers_dict (dict): Dictionary of layers to fetch. +logging (bool): Enable or disable logging.

+

Returns: +dict: Dictionary of GeoDataFrames for each layer.

+ + +
+ Source code in prettymaps/fetch.py +
572
+573
+574
+575
+576
+577
+578
+579
+580
+581
+582
+583
+584
+585
+586
+587
+588
+589
+590
+591
+592
+593
+594
+595
+596
+597
+598
+599
+600
+601
+602
+603
+604
+605
+606
+607
+608
+609
+610
+611
+612
+613
+614
+615
+616
+617
+618
+619
+620
+621
+622
+623
+624
+625
+626
+627
+628
+629
+630
+631
+632
+633
+634
+635
+636
+637
+638
+639
+640
+641
+642
+643
+644
+645
+646
+647
+648
+649
+650
+651
+652
+653
+654
+655
+656
+657
+658
+659
+660
+661
+662
+663
+664
+665
+666
+667
+668
+669
+670
+671
+672
+673
+674
+675
+676
+677
+678
+679
+680
+681
+682
+683
+684
+685
+686
+687
+688
+689
+690
+691
+692
+693
+694
+695
+696
def unified_osm_request(
+    perimeter: GeoDataFrame, layers_dict: dict, logging: bool = False
+) -> dict:
+    """
+    Unify all OSM requests into one to improve efficiency.
+
+    Parameters:
+    perimeter (GeoDataFrame): The perimeter GeoDataFrame.
+    layers_dict (dict): Dictionary of layers to fetch.
+    logging (bool): Enable or disable logging.
+
+    Returns:
+    dict: Dictionary of GeoDataFrames for each layer.
+    """
+    # Apply tolerance to the perimeter
+    perimeter_with_tolerance = ox.project_gdf(perimeter).buffer(0).to_crs(4326)
+    perimeter_with_tolerance = unary_union(perimeter_with_tolerance.geometry).buffer(0)
+
+    # Fetch from perimeter's bounding box, to avoid missing some geometries
+    bbox = box(*perimeter_with_tolerance.bounds)
+
+    # Initialize the result dictionary
+    gdfs = {}
+    ## Read layers from cache
+    # for layer, kwargs in layers_dict.items():
+    #    gdf = read_from_cache(perimeter, layers_dict[layer])
+    #    if gdf is not None:
+    #        gdfs[layer] = gdf
+
+    # Combine all tags into a single dictionary for a unified request
+    combined_tags = merge_tags(
+        {layer: kwargs for layer, kwargs in layers_dict.items() if layer not in gdfs}
+    )
+
+    # Fetch all features in one request
+    try:
+        all_features = ox.features_from_polygon(bbox, tags=combined_tags)
+    except Exception as e:
+        all_features = GeoDataFrame(geometry=[])
+
+    # Split the features into separate GeoDataFrames based on the layers_dict
+    for layer, kwargs in layers_dict.items():
+        if layer in gdfs:
+            continue
+        try:
+            if layer in ["streets", "railway", "waterway"]:
+                graph = ox.graph_from_polygon(
+                    bbox,
+                    custom_filter=kwargs.get("custom_filter"),
+                    truncate_by_edge=True,
+                )
+                gdf = ox.graph_to_gdfs(graph, nodes=False)
+            elif layer == "sea":
+                try:
+                    coastline = unary_union(
+                        ox.features_from_polygon(
+                            bbox, tags={"natural": "coastline"}
+                        ).geometry.tolist()
+                    )
+                    sea_candidates = bbox.difference(coastline.buffer(1e-9)).geoms
+                    drive = ox.graph_from_polygon(bbox, network_type="drive")
+                    drive = ox.graph_to_gdfs(drive, nodes=False)
+
+                    def filter_candidate(sea_candidate):
+                        intersections = drive.geometry.intersects(sea_candidate)
+                        if "bridge" in drive.columns:
+                            return not any(
+                                intersections
+                                & (
+                                    drive.loc[
+                                        drive.geometry.intersects(sea_candidate),
+                                        "bridge",
+                                    ]
+                                    != "yes"
+                                )
+                            )
+                        else:
+                            return not any(intersections)
+
+                    sea = unary_union(
+                        MultiPolygon(
+                            [
+                                candidate
+                                for candidate in sea_candidates
+                                if filter_candidate(candidate)
+                            ]
+                        ).geoms
+                    ).buffer(1e-8)
+                    gdf = GeoDataFrame(geometry=[sea], crs=perimeter.crs)
+                except:
+                    gdf = GeoDataFrame(geometry=[], crs=perimeter.crs)
+            else:
+                if kwargs.get("osmid") is None:
+                    if layer == "perimeter":
+                        gdf = perimeter
+                    else:
+                        layer_tags = kwargs.get("tags")
+                        gdf = gp.GeoDataFrame(geometry=[], crs=perimeter.crs)
+                        for key, value in layer_tags.items():
+                            if isinstance(value, bool) and value:
+                                filtered_features = all_features[
+                                    ~pd.isna(all_features[key])
+                                ]
+                            elif isinstance(value, list):
+                                filtered_features = all_features[
+                                    all_features[key].isin(value)
+                                ]
+                            else:
+                                filtered_features = all_features[
+                                    all_features[key] == value
+                                ]
+                            gdf = pd.concat([gdf, filtered_features], axis=0)
+                else:
+                    gdf = ox.geocode_to_gdf(kwargs.get("osmid"), by_osmid=True)
+
+            gdf = gdf.copy()
+            gdf.geometry = gdf.geometry.intersection(perimeter_with_tolerance)
+            gdf.drop(gdf[gdf.geometry.is_empty].index, inplace=True)
+            gdfs[layer] = gdf
+            # write_to_cache(perimeter, gdf, layers_dict[layer])
+        except Exception as e:
+            # print(f"Error fetching {layer}: {e}")
+            gdfs[layer] = GeoDataFrame(geometry=[])
+
+    return gdfs
+
+
+
+ +
+ +
+ + +

+ write_to_cache(perimeter, gdf, layer_kwargs, cache_dir='prettymaps_cache') + +

+ + +
+ +

Write a GeoDataFrame to the cache based on the perimeter and layer arguments.

+

Parameters: +perimeter (GeoDataFrame): The perimeter GeoDataFrame. +gdf (GeoDataFrame): The GeoDataFrame to cache. +layer_kwargs (dict): Dictionary of layer arguments. +cache_dir (str): Directory to save the cached GeoDataFrame.

+ + +
+ Source code in prettymaps/fetch.py +
314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+338
+339
+340
+341
+342
+343
+344
+345
def write_to_cache(
+    perimeter: GeoDataFrame,
+    gdf: GeoDataFrame,
+    layer_kwargs: dict,
+    cache_dir: str = "prettymaps_cache",
+):
+    """
+    Write a GeoDataFrame to the cache based on the perimeter and layer arguments.
+
+    Parameters:
+    perimeter (GeoDataFrame): The perimeter GeoDataFrame.
+    gdf (GeoDataFrame): The GeoDataFrame to cache.
+    layer_kwargs (dict): Dictionary of layer arguments.
+    cache_dir (str): Directory to save the cached GeoDataFrame.
+    """
+    np.random.seed(0)
+    os.makedirs(cache_dir, exist_ok=True)
+
+    # Create hash from perimeter
+    perimeter_hash = hash(perimeter["geometry"].to_json())
+    # Create hash from kwargs
+    kwargs_hash = hash(str(layer_kwargs))
+    # Join hashes
+    hash_str = f"{perimeter_hash}_{kwargs_hash}"
+
+    cache_path = os.path.join(cache_dir, f"{hash_str}.geojson")
+
+    # Write gdf to cache
+    logging.getLogger().setLevel(logging.CRITICAL)
+    if not gdf.empty:
+        gdf.to_file(cache_path, driver="GeoJSON")
+    logging.getLogger().setLevel(logging.INFO)
+
+
+
+ +
+ + + +
+ +
+ +
diff --git a/index.html b/index.html index 2dd750e..557b7b5 100644 --- a/index.html +++ b/index.html @@ -213,6 +213,19 @@ + + @@ -225,6 +238,43 @@ + + + + @@ -293,6 +343,32 @@ + + + @@ -308,7 +384,23 @@

Prettymaps Documentation

-

Welcome to the Prettymaps documentation site.

+

Welcome to the Prettymaps documentation site.

+

Prettymaps is a minimal Python library to draw beautiful maps from OpenStreetMap data.
+It allows you to easily generate artistic, customizable maps for any location in the world.

+

Features

+ +

Quick Example

+
import prettymaps
+
+prettymaps.plot("Porto Alegre, Brazil")
+
+

Explore the Usage and API Reference for more details.

diff --git a/objects.inv b/objects.inv index e2c9fb411211f4ba24e6f0f26ccfffd120bba4e0..3ef60c4034bee64a2e861bb6b272c5735439eb49 100644 GIT binary patch delta 334 zcmV-U0kQss0m1{2eSe*l-A=VlXw8jOWZbE{&1X)zCG>6CMKrh z?j~2~bB^tkWG0a@U@om?JT!TH56M{HyJ zm|->00=Vb|U?#g7jkpvSVWvVlV;^;!wM`f%6oM_9UC*gTtAF+L1Llh@4KyDxk=Ct4 zguc{3Z#$3#Wxz~+R8y1!2|Frx!2&xPb8=D274&w3egPfL+rK7mRAkJ<9Zmv9?9W>% zYN~p1=g4S)v=wv_$kQH=T20S?pc{4f#Oy2qa<6uOfig0NJFry|UNfdy3sWA|`qSFL zIwm8D)oY*LqDNiXhcL#{B@VMe&CrfZ{PzpJ6>nwjHV{wQ73}rMVi1?W4ami;jk0|Pe$Hvl2F16lw8 diff --git a/search/search_index.json b/search/search_index.json index 12441bd..4baed99 100644 --- a/search/search_index.json +++ b/search/search_index.json @@ -1 +1 @@ -{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"Prettymaps Documentation","text":"

Welcome to the Prettymaps documentation site.

"},{"location":"api/","title":"API Reference","text":"

This page will contain the API reference for Prettymaps.

More details coming soon!

"},{"location":"usage/","title":"Usage","text":"

This page will describe how to use Prettymaps.

More details coming soon!

"}]} \ No newline at end of file +{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"Prettymaps Documentation","text":"

Welcome to the Prettymaps documentation site.

Prettymaps is a minimal Python library to draw beautiful maps from OpenStreetMap data. It allows you to easily generate artistic, customizable maps for any location in the world.

"},{"location":"#features","title":"Features","text":"
  • Fetches and visualizes OpenStreetMap data with minimal code
  • Highly customizable layers and styles
  • Supports elevation, hillshading, and keypoints
  • Preset system for reusable map styles
  • Export to PNG, SVG, and plotter-friendly formats
"},{"location":"#quick-example","title":"Quick Example","text":"
import prettymaps\n\nprettymaps.plot(\"Porto Alegre, Brazil\")\n

Explore the Usage and API Reference for more details.

"},{"location":"api/","title":"API Reference","text":"

This section is auto-generated from the Prettymaps source code.

"},{"location":"api/#prettymaps.draw","title":"prettymaps.draw","text":"

Prettymaps - A minimal Python library to draw pretty maps from OpenStreetMap Data Copyright (C) 2021 Marcelo Prates

This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.

You should have received a copy of the GNU Affero General Public License along with this program. If not, see https://www.gnu.org/licenses/.

"},{"location":"api/#prettymaps.draw.Plot","title":"Plot dataclass","text":"

Dataclass implementing a prettymaps Plot object.

Attributes:

Name Type Description geodataframes Dict[str, GeoDataFrame]

A dictionary of GeoDataFrames (one for each plot layer).

fig Figure

A matplotlib figure.

ax Axes

A matplotlib axis object.

background BaseGeometry

Background layer (shapely object).

keypoints GeoDataFrame

Keypoints GeoDataFrame.

Source code in prettymaps/draw.py
@dataclass\nclass Plot:\n    \"\"\"\n    Dataclass implementing a prettymaps Plot object.\n\n    Attributes:\n        geodataframes (Dict[str, gp.GeoDataFrame]): A dictionary of GeoDataFrames (one for each plot layer).\n        fig (matplotlib.figure.Figure): A matplotlib figure.\n        ax (matplotlib.axes.Axes): A matplotlib axis object.\n        background (BaseGeometry): Background layer (shapely object).\n        keypoints (gp.GeoDataFrame): Keypoints GeoDataFrame.\n    \"\"\"\n\n    geodataframes: Dict[str, gp.GeoDataFrame]\n    fig: matplotlib.figure.Figure\n    ax: matplotlib.axes.Axes\n    background: BaseGeometry\n    keypoints: gp.GeoDataFrame\n
"},{"location":"api/#prettymaps.draw.PolygonPatch","title":"PolygonPatch","text":"

Bases: PathPatch

A class to create a matplotlib PathPatch from a shapely geometry.

Attributes:

Name Type Description shape BaseGeometry

Shapely geometry.

kwargs BaseGeometry

Parameters for matplotlib's PathPatch constructor.

Methods:

Name Description __init__

BaseGeometry, **kwargs): Initialize the PolygonPatch with the given shapely geometry and additional parameters.

shape (BaseGeometry): Shapely geometry. kwargs: Parameters for matplotlib's PathPatch constructor.

Source code in prettymaps/draw.py
class PolygonPatch(PathPatch):\n    \"\"\"\n    A class to create a matplotlib PathPatch from a shapely geometry.\n\n    Attributes:\n        shape (BaseGeometry): Shapely geometry.\n        kwargs: Parameters for matplotlib's PathPatch constructor.\n\n    Methods:\n        __init__(shape: BaseGeometry, **kwargs):\n            Initialize the PolygonPatch with the given shapely geometry and additional parameters.\n\n\n            shape (BaseGeometry): Shapely geometry.\n            kwargs: Parameters for matplotlib's PathPatch constructor.\n    \"\"\"\n\n    def __init__(self, shape: BaseGeometry, **kwargs):\n        \"\"\"\n        Initialize the PolygonPatch.\n\n        Args:\n            shape (BaseGeometry): Shapely geometry\n            kwargs: parameters for matplotlib's PathPatch constructor\n        \"\"\"\n        # Init vertices and codes lists\n        vertices, codes = [], []\n        for geom in shape.geoms if hasattr(shape, \"geoms\") else [shape]:\n            for poly in geom.geoms if hasattr(geom, \"geoms\") else [geom]:\n                if type(poly) != Polygon:\n                    continue\n                # Get polygon's exterior and interiors\n                exterior = np.array(poly.exterior.xy)\n                interiors = [np.array(interior.xy) for interior in poly.interiors]\n                # Append to vertices and codes lists\n                vertices += [exterior] + interiors\n                codes += list(\n                    map(\n                        # Ring coding\n                        lambda p: [Path.MOVETO]\n                        + [Path.LINETO] * (p.shape[1] - 2)\n                        + [Path.CLOSEPOLY],\n                        [exterior] + interiors,\n                    )\n                )\n        # Initialize PathPatch with the generated Path\n        super().__init__(\n            Path(np.concatenate(vertices, 1).T, np.concatenate(codes)), **kwargs\n        )\n
"},{"location":"api/#prettymaps.draw.PolygonPatch.__init__","title":"__init__(shape, **kwargs)","text":"

Initialize the PolygonPatch.

Parameters:

Name Type Description Default shape BaseGeometry

Shapely geometry

required kwargs

parameters for matplotlib's PathPatch constructor

{} Source code in prettymaps/draw.py
def __init__(self, shape: BaseGeometry, **kwargs):\n    \"\"\"\n    Initialize the PolygonPatch.\n\n    Args:\n        shape (BaseGeometry): Shapely geometry\n        kwargs: parameters for matplotlib's PathPatch constructor\n    \"\"\"\n    # Init vertices and codes lists\n    vertices, codes = [], []\n    for geom in shape.geoms if hasattr(shape, \"geoms\") else [shape]:\n        for poly in geom.geoms if hasattr(geom, \"geoms\") else [geom]:\n            if type(poly) != Polygon:\n                continue\n            # Get polygon's exterior and interiors\n            exterior = np.array(poly.exterior.xy)\n            interiors = [np.array(interior.xy) for interior in poly.interiors]\n            # Append to vertices and codes lists\n            vertices += [exterior] + interiors\n            codes += list(\n                map(\n                    # Ring coding\n                    lambda p: [Path.MOVETO]\n                    + [Path.LINETO] * (p.shape[1] - 2)\n                    + [Path.CLOSEPOLY],\n                    [exterior] + interiors,\n                )\n            )\n    # Initialize PathPatch with the generated Path\n    super().__init__(\n        Path(np.concatenate(vertices, 1).T, np.concatenate(codes)), **kwargs\n    )\n
"},{"location":"api/#prettymaps.draw.Preset","title":"Preset dataclass","text":"

Dataclass implementing a prettymaps Preset object.

Attributes:

Name Type Description params dict

Dictionary of prettymaps.plot() parameters.

Source code in prettymaps/draw.py
@dataclass\nclass Preset:\n    \"\"\"\n    Dataclass implementing a prettymaps Preset object.\n\n    Attributes:\n        params (dict): Dictionary of prettymaps.plot() parameters.\n    \"\"\"\n\n    params: dict\n
"},{"location":"api/#prettymaps.draw.Subplot","title":"Subplot","text":"

Class implementing a prettymaps Subplot. Attributes: - query: prettymaps.plot() query - kwargs: dictionary of prettymaps.plot() parameters

Source code in prettymaps/draw.py
class Subplot:\n    \"\"\"\n    Class implementing a prettymaps Subplot. Attributes:\n    - query: prettymaps.plot() query\n    - kwargs: dictionary of prettymaps.plot() parameters\n    \"\"\"\n\n    def __init__(self, query, **kwargs):\n        self.query = query\n        self.kwargs = kwargs\n
"},{"location":"api/#prettymaps.draw.create_background","title":"create_background(gdfs, style, logging=False)","text":"

Create a background layer given a collection of GeoDataFrames

Parameters:

Name Type Description Default gdfs Dict[str, GeoDataFrame]

Dictionary of GeoDataFrames

required style Dict[str, dict]

Dictionary of matplotlib style parameters

required

Returns:

Type Description Tuple[BaseGeometry, float, float, float, float, float, float]

Tuple[BaseGeometry, float, float, float, float, float, float]: background geometry, bounds, width and height

Source code in prettymaps/draw.py
@log_execution_time\ndef create_background(\n    gdfs: Dict[str, gp.GeoDataFrame],\n    style: Dict[str, dict],\n    logging=False,\n) -> Tuple[BaseGeometry, float, float, float, float, float, float]:\n    \"\"\"\n    Create a background layer given a collection of GeoDataFrames\n\n    Args:\n        gdfs (Dict[str, gp.GeoDataFrame]): Dictionary of GeoDataFrames\n        style (Dict[str, dict]): Dictionary of matplotlib style parameters\n\n    Returns:\n        Tuple[BaseGeometry, float, float, float, float, float, float]: background geometry, bounds, width and height\n    \"\"\"\n\n    # Create background\n    background_pad = 1.1\n    if \"background\" in style and \"pad\" in style[\"background\"]:\n        background_pad = style[\"background\"].pop(\"pad\")\n\n    background = shapely.affinity.scale(\n        box(\n            *shapely.ops.unary_union(ox.project_gdf(gdfs[\"perimeter\"]).geometry).bounds\n        ),\n        background_pad,\n        background_pad,\n    )\n\n    if \"background\" in style and \"dilate\" in style[\"background\"]:\n        background = background.buffer(style[\"background\"].pop(\"dilate\"))\n\n    # Get bounds\n    xmin, ymin, xmax, ymax = background.bounds\n    dx, dy = xmax - xmin, ymax - ymin\n\n    return background, xmin, ymin, xmax, ymax, dx, dy\n
"},{"location":"api/#prettymaps.draw.create_preset","title":"create_preset(name, layers=None, style=None, circle=None, radius=None, dilate=None)","text":"

Create a preset file and save it on the presets folder (prettymaps/presets/) under name 'name.json'

Parameters:

Name Type Description Default name str

Preset name

required layers Dict[str, dict]

prettymaps.plot() 'layers' parameter dict. Defaults to None.

None style Dict[str, dict]

prettymaps.plot() 'style' parameter dict. Defaults to None.

None circle Optional[bool]

prettymaps.plot() 'circle' parameter. Defaults to None.

None radius Optional[Union[float, bool]]

prettymaps.plot() 'radius' parameter. Defaults to None.

None dilate Optional[Union[float, bool]]

prettymaps.plot() 'dilate' parameter. Defaults to None.

None Source code in prettymaps/draw.py
def create_preset(\n    name: str,\n    layers: Optional[Dict[str, dict]] = None,\n    style: Optional[Dict[str, dict]] = None,\n    circle: Optional[bool] = None,\n    radius: Optional[Union[float, bool]] = None,\n    dilate: Optional[Union[float, bool]] = None,\n) -> None:\n    \"\"\"\n    Create a preset file and save it on the presets folder (prettymaps/presets/) under name 'name.json'\n\n    Args:\n        name (str): Preset name\n        layers (Dict[str, dict], optional): prettymaps.plot() 'layers' parameter dict. Defaults to None.\n        style (Dict[str, dict], optional): prettymaps.plot() 'style' parameter dict. Defaults to None.\n        circle (Optional[bool], optional): prettymaps.plot() 'circle' parameter. Defaults to None.\n        radius (Optional[Union[float, bool]], optional): prettymaps.plot() 'radius' parameter. Defaults to None.\n        dilate (Optional[Union[float, bool]], optional): prettymaps.plot() 'dilate' parameter. Defaults to None.\n    \"\"\"\n\n    # if not os.path.isdir('presets'):\n    #    os.makedirs('presets')\n\n    path = os.path.join(presets_directory(), f\"{name}.json\")\n    with open(path, \"w\") as f:\n        json.dump(\n            {\n                \"layers\": layers,\n                \"style\": style,\n                \"circle\": circle,\n                \"radius\": radius,\n                \"dilate\": dilate,\n            },\n            f,\n            ensure_ascii=False,\n        )\n
"},{"location":"api/#prettymaps.draw.draw_credit","title":"draw_credit(ax, background, credit, mode, multiplot, logging=False)","text":"

Draws credit text on the plot.

Parameters:

Name Type Description Default ax Axes

Matplotlib axis object.

required background BaseGeometry

Background layer.

required credit Dict[str, Any]

Dictionary containing credit text and style parameters.

required mode str

Drawing mode. Options: 'matplotlib', 'plotter'.

required multiplot bool

Whether the plot is part of a multiplot.

required logging bool

Whether to enable logging. Defaults to False.

False Source code in prettymaps/draw.py
@log_execution_time\ndef draw_credit(\n    ax: matplotlib.axes.Axes,\n    background: BaseGeometry,\n    credit: Dict[str, Any],\n    mode: str,\n    multiplot: bool,\n    logging: bool = False,\n) -> None:\n    \"\"\"\n    Draws credit text on the plot.\n\n    Args:\n        ax (matplotlib.axes.Axes): Matplotlib axis object.\n        background (BaseGeometry): Background layer.\n        credit (Dict[str, Any]): Dictionary containing credit text and style parameters.\n        mode (str): Drawing mode. Options: 'matplotlib', 'plotter'.\n        multiplot (bool): Whether the plot is part of a multiplot.\n        logging (bool, optional): Whether to enable logging. Defaults to False.\n    \"\"\"\n    if (mode == \"matplotlib\") and (credit != False) and (not multiplot):\n        draw_text(ax, credit, background)\n
"},{"location":"api/#prettymaps.draw.draw_text","title":"draw_text(ax, params, background)","text":"

Draw text with content and matplotlib style parameters specified by 'params' dictionary. params['text'] should contain the message to be drawn.

Parameters:

Name Type Description Default ax Axes

Matplotlib axis object.

required params Dict[str, Any]

Matplotlib style parameters for drawing text. params['text'] should contain the message to be drawn.

required background BaseGeometry

Background layer.

required Source code in prettymaps/draw.py
def draw_text(\n    ax: matplotlib.axes.Axes, params: Dict[str, Any], background: BaseGeometry\n) -> None:\n    \"\"\"\n    Draw text with content and matplotlib style parameters specified by 'params' dictionary.\n    params['text'] should contain the message to be drawn.\n\n    Args:\n        ax (matplotlib.axes.Axes): Matplotlib axis object.\n        params (Dict[str, Any]): Matplotlib style parameters for drawing text. params['text'] should contain the message to be drawn.\n        background (BaseGeometry): Background layer.\n    \"\"\"\n    # Override default osm_credit dict with provided parameters\n    params = override_params(\n        dict(\n            text=\"\\n\".join(\n                [\n                    \"data \u00a9 OpenStreetMap contributors\",\n                    \"github.com/marceloprates/prettymaps\",\n                ]\n            ),\n            x=0,\n            y=1,\n            horizontalalignment=\"left\",\n            verticalalignment=\"top\",\n            bbox=dict(boxstyle=\"square\", fc=\"#fff\", ec=\"#000\"),\n            # fontfamily=\"Ubuntu Mono\",\n        ),\n        params,\n    )\n    x, y, text = [params.pop(k) for k in [\"x\", \"y\", \"text\"]]\n\n    # Get background bounds\n    xmin, ymin, xmax, ymax = background.bounds\n\n    x = np.interp([x], [0, 1], [xmin, xmax])[0]\n    y = np.interp([y], [0, 1], [ymin, ymax])[0]\n\n    ax.text(x, y, text, zorder=1000, **params)\n
"},{"location":"api/#prettymaps.draw.gdf_to_shapely","title":"gdf_to_shapely(layer, gdf, width=None, point_size=None, line_width=None, **kwargs)","text":"

Convert a dict of GeoDataFrames to a dict of shapely geometries

Parameters:

Name Type Description Default layer str

Layer name

required gdf GeoDataFrame

Input GeoDataFrame

required width Optional[Union[dict, float]]

Street network width. Can be either a dictionary or a float. Defaults to None.

None point_size Optional[float]

Point geometries (1D) will be dilated by this amount. Defaults to None.

None line_width Optional[float]

Line geometries (2D) will be dilated by this amount. Defaults to None.

None

Returns:

Name Type Description GeometryCollection GeometryCollection

Output GeoDataFrame

Source code in prettymaps/draw.py
def gdf_to_shapely(\n    layer: str,\n    gdf: gp.GeoDataFrame,\n    width: Optional[Union[dict, float]] = None,\n    point_size: Optional[float] = None,\n    line_width: Optional[float] = None,\n    **kwargs,\n) -> GeometryCollection:\n    \"\"\"\n    Convert a dict of GeoDataFrames to a dict of shapely geometries\n\n    Args:\n        layer (str): Layer name\n        gdf (gp.GeoDataFrame): Input GeoDataFrame\n        width (Optional[Union[dict, float]], optional): Street network width. Can be either a dictionary or a float. Defaults to None.\n        point_size (Optional[float], optional): Point geometries (1D) will be dilated by this amount. Defaults to None.\n        line_width (Optional[float], optional): Line geometries (2D) will be dilated by this amount. Defaults to None.\n\n    Returns:\n        GeometryCollection: Output GeoDataFrame\n    \"\"\"\n\n    # Project gdf if applicable\n    if not gdf.empty and gdf.crs is not None:\n        gdf = ox.project_gdf(gdf)\n\n    if layer in [\"streets\", \"railway\", \"waterway\"]:\n        geometries = graph_to_shapely(gdf, width)\n    else:\n        geometries = geometries_to_shapely(\n            gdf, point_size=point_size, line_width=line_width\n        )\n\n    return geometries\n
"},{"location":"api/#prettymaps.draw.geometries_to_shapely","title":"geometries_to_shapely(gdf, point_size=None, line_width=None)","text":"

Convert geometries in GeoDataFrame to shapely format

Parameters:

Name Type Description Default gdf GeoDataFrame

Input GeoDataFrame

required point_size Optional[float]

Point geometries (1D) will be dilated by this amount. Defaults to None.

None line_width Optional[float]

Line geometries (2D) will be dilated by this amount. Defaults to None.

None

Returns:

Name Type Description GeometryCollection GeometryCollection

Shapely geometries computed from GeoDataFrame geometries

Source code in prettymaps/draw.py
def geometries_to_shapely(\n    gdf: gp.GeoDataFrame,\n    point_size: Optional[float] = None,\n    line_width: Optional[float] = None,\n) -> GeometryCollection:\n    \"\"\"\n    Convert geometries in GeoDataFrame to shapely format\n\n    Args:\n        gdf (gp.GeoDataFrame): Input GeoDataFrame\n        point_size (Optional[float], optional): Point geometries (1D) will be dilated by this amount. Defaults to None.\n        line_width (Optional[float], optional): Line geometries (2D) will be dilated by this amount. Defaults to None.\n\n    Returns:\n        GeometryCollection: Shapely geometries computed from GeoDataFrame geometries\n    \"\"\"\n\n    geoms = gdf.geometry.tolist()\n    collections = [x for x in geoms if type(x) == GeometryCollection]\n    points = [x for x in geoms if type(x) == Point] + [\n        y for x in collections for y in x.geoms if type(y) == Point\n    ]\n    lines = [x for x in geoms if type(x) in [LineString, MultiLineString]] + [\n        y\n        for x in collections\n        for y in x.geoms\n        if type(y) in [LineString, MultiLineString]\n    ]\n    polys = [x for x in geoms if type(x) in [Polygon, MultiPolygon]] + [\n        y for x in collections for y in x.geoms if type(y) in [Polygon, MultiPolygon]\n    ]\n\n    # Convert points into circles with radius \"point_size\"\n    if point_size:\n        points = [x.buffer(point_size) for x in points] if point_size > 0 else []\n    if line_width:\n        lines = [x.buffer(line_width) for x in lines] if line_width > 0 else []\n\n    return GeometryCollection(list(points) + list(lines) + list(polys))\n
"},{"location":"api/#prettymaps.draw.graph_to_shapely","title":"graph_to_shapely(gdf, width=1.0)","text":"

Given a GeoDataFrame containing a graph (street newtork), convert them to shapely geometries by applying dilation given by 'width'

Parameters:

Name Type Description Default gdf GeoDataFrame

input GeoDataFrame containing graph (street network) geometries

required width float

Line geometries will be dilated by this amount. Defaults to 1..

1.0

Returns:

Name Type Description BaseGeometry BaseGeometry

Shapely

Source code in prettymaps/draw.py
def graph_to_shapely(gdf: gp.GeoDataFrame, width: float = 1.0) -> BaseGeometry:\n    \"\"\"\n    Given a GeoDataFrame containing a graph (street newtork),\n    convert them to shapely geometries by applying dilation given by 'width'\n\n    Args:\n        gdf (gp.GeoDataFrame): input GeoDataFrame containing graph (street network) geometries\n        width (float, optional): Line geometries will be dilated by this amount. Defaults to 1..\n\n    Returns:\n        BaseGeometry: Shapely\n    \"\"\"\n\n    def highway_to_width(highway):\n        if (type(highway) == str) and (highway in width):\n            return width[highway]\n        elif isinstance(highway, Iterable):\n            for h in highway:\n                if h in width:\n                    return width[h]\n            return np.nan\n        else:\n            return np.nan\n\n    # Annotate GeoDataFrame with the width for each highway type\n    gdf[\"width\"] = (\n        gdf[\"highway\"].map(highway_to_width) if type(width) == dict else width\n    )\n\n    # Remove rows with inexistent width\n    gdf.drop(gdf[gdf.width.isna()].index, inplace=True)\n\n    with warnings.catch_warnings():\n        # Supress shapely.errors.ShapelyDeprecationWarning\n        warnings.simplefilter(\"ignore\", shapely.errors.ShapelyDeprecationWarning)\n        if not all(gdf.width.isna()):\n            # Dilate geometries based on their width\n            gdf.geometry = gdf.apply(\n                lambda row: row[\"geometry\"].buffer(row.width), axis=1\n            )\n\n    return shapely.ops.unary_union(gdf.geometry)\n
"},{"location":"api/#prettymaps.draw.manage_presets","title":"manage_presets(load_preset, save_preset, update_preset, layers, style, circle, radius, dilate, logging=False)","text":"

summary

Parameters:

Name Type Description Default load_preset Optional[str]

Load preset named 'load_preset', if provided

required save_preset Optional[str]

Save preset to file named 'save_preset', if provided

required update_preset Optional[str]

Load, update and save preset named 'update_preset', if provided

required layers Dict[str, dict]

prettymaps.plot() 'layers' parameter dict

required style Dict[str, dict]

prettymaps.plot() 'style' parameter dict

required circle Optional[bool]

prettymaps.plot() 'circle' parameter

required radius Optional[Union[float, bool]]

prettymaps.plot() 'radius' parameter

required dilate Optional[Union[float, bool]]

prettymaps.plot() 'dilate' parameter

required

Returns:

Type Description Tuple[dict, dict, Optional[float], Optional[Union[float, bool]], Optional[Union[float, bool]]]

Tuple[dict, dict, Optional[float], Optional[Union[float, bool]], Optional[Union[float, bool]]]: Updated layers, style, circle, radius, dilate parameters

Source code in prettymaps/draw.py
@log_execution_time\ndef manage_presets(\n    load_preset: Optional[str],\n    save_preset: bool,\n    update_preset: Optional[str],\n    layers: Dict[str, dict],\n    style: Dict[str, dict],\n    circle: Optional[bool],\n    radius: Optional[Union[float, bool]],\n    dilate: Optional[Union[float, bool]],\n    logging=False,\n) -> Tuple[\n    dict,\n    dict,\n    Optional[float],\n    Optional[Union[float, bool]],\n    Optional[Union[float, bool]],\n]:\n    \"\"\"_summary_\n\n    Args:\n        load_preset (Optional[str]): Load preset named 'load_preset', if provided\n        save_preset (Optional[str]): Save preset to file named 'save_preset', if provided\n        update_preset (Optional[str]): Load, update and save preset named 'update_preset', if provided\n        layers (Dict[str, dict]): prettymaps.plot() 'layers' parameter dict\n        style (Dict[str, dict]): prettymaps.plot() 'style' parameter dict\n        circle (Optional[bool]): prettymaps.plot() 'circle' parameter\n        radius (Optional[Union[float, bool]]): prettymaps.plot() 'radius' parameter\n        dilate (Optional[Union[float, bool]]): prettymaps.plot() 'dilate' parameter\n\n    Returns:\n        Tuple[dict, dict, Optional[float], Optional[Union[float, bool]], Optional[Union[float, bool]]]: Updated layers, style, circle, radius, dilate parameters\n    \"\"\"\n\n    # Update preset mode: load a preset, update it with additional parameters and update the JSON file\n    if update_preset is not None:\n        # load_preset = save_preset = True\n        load_preset = save_preset = update_preset\n\n    # Load preset (if provided)\n    if load_preset is not None:\n        layers, style, circle, radius, dilate = override_preset(\n            load_preset, layers, style, circle, radius, dilate\n        )\n\n    # Save parameters as preset\n    if save_preset is not None:\n        create_preset(\n            save_preset,\n            layers=layers,\n            style=style,\n            circle=circle,\n            radius=radius,\n            dilate=dilate,\n        )\n\n    return layers, style, circle, radius, dilate\n
"},{"location":"api/#prettymaps.draw.multiplot","title":"multiplot(*subplots, figsize=None, credit={}, **kwargs)","text":"

Creates a multiplot using the provided subplots and optional parameters.

"},{"location":"api/#prettymaps.draw.multiplot--parameters","title":"Parameters:","text":"

subplots : list A list of subplot objects to be plotted. figsize : tuple, optional A tuple specifying the figure size (width, height) in inches. credit : dict, optional A dictionary containing credit information for the plot. *kwargs : dict, optional Additional keyword arguments to customize the plot.

"},{"location":"api/#prettymaps.draw.multiplot--returns","title":"Returns:","text":"

None

Source code in prettymaps/draw.py
def multiplot(*subplots, figsize=None, credit={}, **kwargs):\n    \"\"\"\n    Creates a multiplot using the provided subplots and optional parameters.\n\n    Parameters:\n    -----------\n    *subplots : list\n        A list of subplot objects to be plotted.\n    figsize : tuple, optional\n        A tuple specifying the figure size (width, height) in inches.\n    credit : dict, optional\n        A dictionary containing credit information for the plot.\n    **kwargs : dict, optional\n        Additional keyword arguments to customize the plot.\n\n    Returns:\n    --------\n    None\n    \"\"\"\n\n    fig = plt.figure(figsize=figsize)\n    ax = plt.subplot(111, aspect=\"equal\")\n\n    mode = \"plotter\" if \"plotter\" in kwargs and kwargs[\"plotter\"] else \"matplotlib\"\n\n    subplots_results = [\n        plot(\n            subplot.query,\n            ax=ax,\n            multiplot=True,\n            **override_params(\n                subplot.kwargs,\n                {\n                    k: v\n                    for k, v in kwargs.items()\n                    if k != \"load_preset\" or \"load_preset\" not in subplot.kwargs\n                },\n            ),\n            show=False,\n        )\n        for subplot in subplots\n    ]\n\n    if mode == \"matplotlib\":\n        ax.axis(\"off\")\n        ax.axis(\"equal\")\n        ax.autoscale()\n
"},{"location":"api/#prettymaps.draw.override_args","title":"override_args(layers, circle, dilate, logging=False)","text":"

Override arguments in layers' kwargs

Parameters:

Name Type Description Default layers dict

prettymaps.plot() Layers parameters dict

required circle Optional[bool]

prettymaps.plot() 'Circle' parameter

required dilate Optional[Union[float, bool]]

prettymaps.plot() 'dilate' parameter

required

Returns:

Name Type Description dict dict

output dict

Source code in prettymaps/draw.py
@log_execution_time\ndef override_args(\n    layers: dict,\n    circle: Optional[bool],\n    dilate: Optional[Union[float, bool]],\n    logging=False,\n) -> dict:\n    \"\"\"\n    Override arguments in layers' kwargs\n\n    Args:\n        layers (dict): prettymaps.plot() Layers parameters dict\n        circle (Optional[bool]): prettymaps.plot() 'Circle' parameter\n        dilate (Optional[Union[float, bool]]): prettymaps.plot() 'dilate' parameter\n\n    Returns:\n        dict: output dict\n    \"\"\"\n    override_args = [\"circle\", \"dilate\"]\n    for layer in layers:\n        for arg in override_args:\n            if arg not in layers[layer]:\n                layers[layer][arg] = locals()[arg]\n    return layers\n
"},{"location":"api/#prettymaps.draw.override_params","title":"override_params(default_dict, new_dict)","text":"

Override parameters in 'default_dict' with additional parameters from 'new_dict'

Parameters:

Name Type Description Default default_dict dict

Default dict to be overriden with 'new_dict' parameters

required new_dict dict

New dict to override 'default_dict' parameters

required

Returns:

Name Type Description dict dict

default_dict overriden with new_dict parameters

Source code in prettymaps/draw.py
def override_params(default_dict: dict, new_dict: dict) -> dict:\n    \"\"\"\n    Override parameters in 'default_dict' with additional parameters from 'new_dict'\n\n    Args:\n        default_dict (dict): Default dict to be overriden with 'new_dict' parameters\n        new_dict (dict): New dict to override 'default_dict' parameters\n\n    Returns:\n        dict: default_dict overriden with new_dict parameters\n    \"\"\"\n\n    final_dict = deepcopy(default_dict)\n\n    for key in new_dict.keys():\n        if type(new_dict[key]) == dict:\n            if key in final_dict:\n                final_dict[key] = override_params(final_dict[key], new_dict[key])\n            else:\n                final_dict[key] = new_dict[key]\n        else:\n            final_dict[key] = new_dict[key]\n\n    return final_dict\n
"},{"location":"api/#prettymaps.draw.override_preset","title":"override_preset(name, layers={}, style={}, circle=None, radius=None, dilate=None)","text":"

Read the preset file given by 'name' and override it with additional parameters

Parameters:

Name Type Description Default name str

description

required layers Dict[str, dict]

description. Defaults to {}.

{} style Dict[str, dict]

description. Defaults to {}.

{} circle Union[float, None]

description. Defaults to None.

None radius Union[float, None]

description. Defaults to None.

None dilate Union[float, None]

description. Defaults to None.

None

Returns:

Type Description Tuple[dict, dict, Optional[float], Optional[Union[float, bool]], Optional[Union[float, bool]]]

Tuple[dict, dict, Optional[float], Optional[Union[float, bool]], Optional[Union[float, bool]]]: Preset parameters overriden by additional provided parameters

Source code in prettymaps/draw.py
def override_preset(\n    name: str,\n    layers: Dict[str, dict] = {},\n    style: Dict[str, dict] = {},\n    circle: Optional[float] = None,\n    radius: Optional[Union[float, bool]] = None,\n    dilate: Optional[Union[float, bool]] = None,\n) -> Tuple[\n    dict,\n    dict,\n    Optional[float],\n    Optional[Union[float, bool]],\n    Optional[Union[float, bool]],\n]:\n    \"\"\"\n    Read the preset file given by 'name' and override it with additional parameters\n\n    Args:\n        name (str): _description_\n        layers (Dict[str, dict], optional): _description_. Defaults to {}.\n        style (Dict[str, dict], optional): _description_. Defaults to {}.\n        circle (Union[float, None], optional): _description_. Defaults to None.\n        radius (Union[float, None], optional): _description_. Defaults to None.\n        dilate (Union[float, None], optional): _description_. Defaults to None.\n\n    Returns:\n        Tuple[dict, dict, Optional[float], Optional[Union[float, bool]], Optional[Union[float, bool]]]: Preset parameters overriden by additional provided parameters\n    \"\"\"\n\n    params = read_preset(name)\n\n    # Override preset with kwargs\n    if \"layers\" in params:\n        layers = override_params(params[\"layers\"], layers)\n    if \"style\" in params:\n        style = override_params(params[\"style\"], style)\n    if circle is None and \"circle\" in params:\n        circle = params[\"circle\"]\n    if radius is None and \"radius\" in params:\n        radius = params[\"radius\"]\n    if dilate is None and \"dilate\" in params:\n        dilate = params[\"dilate\"]\n\n    # Delete layers marked as 'False' in the parameter dict\n    for layer in [key for key in layers.keys() if layers[key] == False]:\n        del layers[layer]\n\n    # Return overriden presets\n    return layers, style, circle, radius, dilate\n
"},{"location":"api/#prettymaps.draw.plot","title":"plot(query, layers={}, style={}, keypoints={}, preset='default', use_preset=True, save_preset=None, update_preset=None, postprocessing=lambda x: x, circle=None, radius=None, dilate=None, save_as=None, fig=None, ax=None, figsize=(11.7, 11.7), credit={}, mode='matplotlib', multiplot=False, show=True, x=0, y=0, scale_x=1, scale_y=1, rotation=0, logging=False, semantic=False, adjust_aspect_ratio=True)","text":"

Plots a map based on a given query and specified parameters. Args: query: The query for the location to plot. This can be a string (e.g., \"Porto Alegre\"), a tuple of latitude and longitude coordinates, or a custom GeoDataFrame boundary. layers: The OpenStreetMap layers to plot. Defaults to an empty dictionary. style: Matplotlib parameters for drawing each layer. Defaults to an empty dictionary. keypoints: Keypoints to highlight on the map. Defaults to an empty dictionary. preset: Preset configuration to use. Defaults to \"default\". use_preset: Whether to use the preset configuration. Defaults to True. save_preset: Path to save the preset configuration. Defaults to None. update_preset: Path to update the preset configuration with additional parameters. Defaults to None. circle: Whether to use a circular boundary. Defaults to None. radius: Radius for the circular or square boundary. Defaults to None. dilate: Amount to dilate the boundary. Defaults to None. save_as: Path to save the resulting plot. Defaults to None. fig: Matplotlib figure object. Defaults to None. ax: Matplotlib axes object. Defaults to None. title: Title of the plot. Defaults to None. figsize: Size of the figure. Defaults to (11.7, 11.7). constrained_layout: Whether to use constrained layout for the figure. Defaults to True. credit: Parameters for the credit message. Defaults to an empty dictionary. mode: Mode for plotting ('matplotlib' or 'plotter'). Defaults to \"matplotlib\". multiplot: Whether to use multiplot mode. Defaults to False. show: Whether to display the plot using matplotlib. Defaults to True. x: Translation parameter in the x direction. Defaults to 0. y: Translation parameter in the y direction. Defaults to 0. scale_x: Scaling parameter in the x direction. Defaults to 1. scale_y: Scaling parameter in the y direction. Defaults to 1. rotation: Rotation parameter in degrees. Defaults to 0. logging: Whether to enable logging. Defaults to False. Returns: Plot: The resulting plot object.

Source code in prettymaps/draw.py
def plot(\n    query: str | Tuple[float, float] | gp.GeoDataFrame,\n    layers: Dict[str, Dict[str, Any]] = {},\n    style: Dict[str, Dict[str, Any]] = {},\n    keypoints: Dict[str, Any] = {},\n    preset: str = \"default\",\n    use_preset: bool = True,\n    save_preset: str | None = None,\n    update_preset: str | None = None,\n    postprocessing: Callable[\n        [Dict[str, gp.GeoDataFrame]], Dict[str, gp.GeoDataFrame]\n    ] = lambda x: x,\n    circle: bool | None = None,\n    radius: float | bool | None = None,\n    dilate: float | bool | None = None,\n    save_as: str | None = None,\n    fig: plt.Figure | None = None,\n    ax: plt.Axes | None = None,\n    figsize: Tuple[float, float] = (11.7, 11.7),\n    credit: Dict[str, Any] = {},\n    mode: str = \"matplotlib\",\n    multiplot: bool = False,\n    show: bool = True,\n    x: float = 0,\n    y: float = 0,\n    scale_x: float = 1,\n    scale_y: float = 1,\n    rotation: float = 0,\n    logging: bool = False,\n    semantic: bool = False,\n    adjust_aspect_ratio: bool = True,\n) -> Plot:\n    \"\"\"\n    Plots a map based on a given query and specified parameters.\n    Args:\n        query: The query for the location to plot. This can be a string (e.g., \"Porto Alegre\"), a tuple of latitude and longitude coordinates, or a custom GeoDataFrame boundary.\n        layers: The OpenStreetMap layers to plot. Defaults to an empty dictionary.\n        style: Matplotlib parameters for drawing each layer. Defaults to an empty dictionary.\n        keypoints: Keypoints to highlight on the map. Defaults to an empty dictionary.\n        preset: Preset configuration to use. Defaults to \"default\".\n        use_preset: Whether to use the preset configuration. Defaults to True.\n        save_preset: Path to save the preset configuration. Defaults to None.\n        update_preset: Path to update the preset configuration with additional parameters. Defaults to None.\n        circle: Whether to use a circular boundary. Defaults to None.\n        radius: Radius for the circular or square boundary. Defaults to None.\n        dilate: Amount to dilate the boundary. Defaults to None.\n        save_as: Path to save the resulting plot. Defaults to None.\n        fig: Matplotlib figure object. Defaults to None.\n        ax: Matplotlib axes object. Defaults to None.\n        title: Title of the plot. Defaults to None.\n        figsize: Size of the figure. Defaults to (11.7, 11.7).\n        constrained_layout: Whether to use constrained layout for the figure. Defaults to True.\n        credit: Parameters for the credit message. Defaults to an empty dictionary.\n        mode: Mode for plotting ('matplotlib' or 'plotter'). Defaults to \"matplotlib\".\n        multiplot: Whether to use multiplot mode. Defaults to False.\n        show: Whether to display the plot using matplotlib. Defaults to True.\n        x: Translation parameter in the x direction. Defaults to 0.\n        y: Translation parameter in the y direction. Defaults to 0.\n        scale_x: Scaling parameter in the x direction. Defaults to 1.\n        scale_y: Scaling parameter in the y direction. Defaults to 1.\n        rotation: Rotation parameter in degrees. Defaults to 0.\n        logging: Whether to enable logging. Defaults to False.\n    Returns:\n        Plot: The resulting plot object.\n    \"\"\"\n\n    # 1. Manage presets\n    layers, style, circle, radius, dilate = manage_presets(\n        preset if use_preset else None,\n        save_preset,\n        update_preset,\n        layers,\n        style,\n        circle,\n        radius,\n        dilate,\n    )\n\n    # 2. Init matplotlib figure & axis and vsketch object\n    fig, ax, vsk = init_plot(\n        layers,\n        fig,\n        ax,\n        figsize,\n        mode,\n        adjust_aspect_ratio=adjust_aspect_ratio,\n        logging=logging,\n    )\n\n    # 3. Override arguments in layers' kwargs dict\n    layers = override_args(layers, circle, dilate, logging=logging)\n\n    # 4. Fetch geodataframes\n    start_time = time.time()\n    gdfs = get_gdfs(query, layers, radius, dilate, -rotation, logging=logging)\n    fetch_time = time.time() - start_time\n    print(f\"Fetching geodataframes took {fetch_time:.2f} seconds\")\n\n    # 5. Apply transformations to GeoDataFrames (translation, scale, rotation)\n    gdfs = transform_gdfs(gdfs, x, y, scale_x, scale_y, rotation, logging=logging)\n\n    # 6. Apply a postprocessing function to the GeoDataFrames, if provided\n    gdfs = postprocessing(gdfs)\n\n    # 7. Create background GeoDataFrame and get (x,y) bounds\n    background, xmin, ymin, xmax, ymax, dx, dy = create_background(\n        gdfs, style, logging=logging\n    )\n\n    # 8. Draw layers\n    draw_layers(layers, gdfs, style, fig, ax, vsk, mode, logging=logging)\n\n    # 9. Draw keypoints\n    keypoints = draw_keypoints(keypoints, gdfs, ax, logging=logging)\n\n    # 9. Draw background\n    draw_background(background, ax, style, mode, logging=logging)\n\n    # 10. Draw credit message\n    draw_credit(ax, background, credit, mode, multiplot, logging=logging)\n\n    # 11. Draw hillshade\n    draw_hillshade(\n        layers,\n        gdfs,\n        ax,\n        logging=logging,\n        **(layers[\"hillshade\"] if \"hillshade\" in layers else {}),\n    )\n\n    # 12. Create Plot object\n    plot = Plot(gdfs, fig, ax, background, keypoints)\n\n    # 13. Save plot as image\n    if save_as is not None:\n        plt.savefig(save_as)\n\n    # 14. Show plot\n    if show:\n        if mode == \"plotter\":\n            vsk.display()\n        elif mode == \"matplotlib\":\n            plt.show()\n        else:\n            raise Exception(f\"Unknown mode {mode}\")\n    else:\n        plt.close()\n\n    return plot\n
"},{"location":"api/#prettymaps.draw.plot_gdf","title":"plot_gdf(layer, gdf, ax, mode='matplotlib', vsk=None, palette=None, width=None, union=False, dilate_points=None, dilate_lines=None, **kwargs)","text":"

Plot a layer

Parameters:

Name Type Description Default layer str

layer name

required gdf GeoDataFrame

GeoDataFrame

required ax Axes

matplotlib axis object

required mode str

drawing mode. Options: 'matplotlib', 'vsketch'. Defaults to 'matplotlib'

'matplotlib' vsk Optional[SketchClass]

Vsketch object. Mandatory if mode == 'plotter'

None palette Optional[List[str]]

Color palette. Defaults to None.

None width Optional[Union[dict, float]]

Street widths. Either a dictionary or a float. Defaults to None.

None union bool

Whether to join geometries. Defaults to False.

False dilate_points Optional[float]

Amount of dilation to be applied to point (1D) geometries. Defaults to None.

None dilate_lines Optional[float]

Amount of dilation to be applied to line (2D) geometries. Defaults to None.

None

Raises:

Type Description Exception

description

Source code in prettymaps/draw.py
def plot_gdf(\n    layer: str,\n    gdf: gp.GeoDataFrame,\n    ax: matplotlib.axes.Axes,\n    mode: str = \"matplotlib\",\n    # vsk: Optional[vsketch.SketchClass] = None,\n    vsk=None,\n    palette: Optional[List[str]] = None,\n    width: Optional[Union[dict, float]] = None,\n    union: bool = False,\n    dilate_points: Optional[float] = None,\n    dilate_lines: Optional[float] = None,\n    **kwargs,\n) -> None:\n    \"\"\"\n    Plot a layer\n\n    Args:\n        layer (str): layer name\n        gdf (gp.GeoDataFrame): GeoDataFrame\n        ax (matplotlib.axes.Axes): matplotlib axis object\n        mode (str): drawing mode. Options: 'matplotlib', 'vsketch'. Defaults to 'matplotlib'\n        vsk (Optional[vsketch.SketchClass]): Vsketch object. Mandatory if mode == 'plotter'\n        palette (Optional[List[str]], optional): Color palette. Defaults to None.\n        width (Optional[Union[dict, float]], optional): Street widths. Either a dictionary or a float. Defaults to None.\n        union (bool, optional): Whether to join geometries. Defaults to False.\n        dilate_points (Optional[float], optional): Amount of dilation to be applied to point (1D) geometries. Defaults to None.\n        dilate_lines (Optional[float], optional): Amount of dilation to be applied to line (2D) geometries. Defaults to None.\n\n    Raises:\n        Exception: _description_\n    \"\"\"\n\n    # Get hatch and hatch_c parameter\n    hatch_c = kwargs.pop(\"hatch_c\") if \"hatch_c\" in kwargs else None\n\n    # Convert GDF to shapely geometries\n    geometries = gdf_to_shapely(\n        layer, gdf, width, point_size=dilate_points, line_width=dilate_lines\n    )\n\n    # Unite geometries\n    if union:\n        geometries = shapely.ops.unary_union(GeometryCollection([geometries]))\n\n    if (palette is None) and (\"fc\" in kwargs) and (type(kwargs[\"fc\"]) != str):\n        palette = kwargs.pop(\"fc\")\n\n    for shape in geometries.geoms if hasattr(geometries, \"geoms\") else [geometries]:\n        if mode == \"matplotlib\":\n            if type(shape) in [Polygon, MultiPolygon]:\n                # Plot main shape (without silhouette)\n                ax.add_patch(\n                    PolygonPatch(\n                        shape,\n                        lw=0,\n                        ec=(\n                            hatch_c\n                            if hatch_c\n                            else kwargs[\"ec\"] if \"ec\" in kwargs else None\n                        ),\n                        fc=(\n                            kwargs[\"fc\"]\n                            if \"fc\" in kwargs\n                            else np.random.choice(palette) if palette else None\n                        ),\n                        **{\n                            k: v\n                            for k, v in kwargs.items()\n                            if k not in [\"lw\", \"ec\", \"fc\"]\n                        },\n                    ),\n                )\n                # Plot just silhouette\n                ax.add_patch(\n                    PolygonPatch(\n                        shape,\n                        fill=False,\n                        **{\n                            k: v\n                            for k, v in kwargs.items()\n                            if k not in [\"hatch\", \"fill\"]\n                        },\n                    )\n                )\n            elif type(shape) == LineString:\n                ax.plot(\n                    *shape.xy,\n                    c=kwargs[\"ec\"] if \"ec\" in kwargs else None,\n                    **{\n                        k: v\n                        for k, v in kwargs.items()\n                        if k in [\"lw\", \"ls\", \"dashes\", \"zorder\"]\n                    },\n                )\n            elif type(shape) == MultiLineString:\n                for c in shape.geoms:\n                    ax.plot(\n                        *c.xy,\n                        c=kwargs[\"ec\"] if \"ec\" in kwargs else None,\n                        **{\n                            k: v\n                            for k, v in kwargs.items()\n                            if k in [\"lw\", \"lt\", \"dashes\", \"zorder\"]\n                        },\n                    )\n        elif mode == \"plotter\":\n            if (\"draw\" not in kwargs) or kwargs[\"draw\"]:\n\n                # Set stroke\n                if \"stroke\" in kwargs:\n                    vsk.stroke(kwargs[\"stroke\"])\n                else:\n                    vsk.stroke(1)\n\n                # Set pen width\n                if \"penWidth\" in kwargs:\n                    vsk.penWidth(kwargs[\"penWidth\"])\n                else:\n                    vsk.penWidth(0.3)\n\n                if \"fill\" in kwargs:\n                    vsk.fill(kwargs[\"fill\"])\n                else:\n                    vsk.noFill()\n\n                vsk.geometry(shape)\n        else:\n            raise Exception(f\"Unknown mode {mode}\")\n
"},{"location":"api/#prettymaps.draw.presets_directory","title":"presets_directory()","text":"

Returns the path to the 'presets' directory. This function constructs the path to the 'presets' directory, which is located in the same directory as the current script file. Returns: str: The full path to the 'presets' directory.

Source code in prettymaps/draw.py
def presets_directory():\n    \"\"\"\n    Returns the path to the 'presets' directory.\n    This function constructs the path to the 'presets' directory, which is\n    located in the same directory as the current script file.\n    Returns:\n        str: The full path to the 'presets' directory.\n    \"\"\"\n\n    return os.path.join(pathlib.Path(__file__).resolve().parent, \"presets\")\n
"},{"location":"api/#prettymaps.draw.read_preset","title":"read_preset(name)","text":"

Read a preset from the presets folder (prettymaps/presets/)

Parameters:

Name Type Description Default name str

Preset name

required

Returns:

Type Description Dict[str, dict]

parameters dictionary

Source code in prettymaps/draw.py
def read_preset(name: str) -> Dict[str, dict]:\n    \"\"\"\n    Read a preset from the presets folder (prettymaps/presets/)\n\n    Args:\n        name (str): Preset name\n\n    Returns:\n        (Dict[str,dict]): parameters dictionary\n    \"\"\"\n    path = os.path.join(presets_directory(), f\"{name}.json\")\n    with open(path, \"r\") as f:\n        # Load params from JSON file\n        params = json.load(f)\n    return params\n
"},{"location":"api/#prettymaps.draw.transform_gdfs","title":"transform_gdfs(gdfs, x=0, y=0, scale_x=1, scale_y=1, rotation=0, logging=False)","text":"

Apply geometric transformations to dictionary of GeoDataFrames

Parameters:

Name Type Description Default gdfs Dict[str, GeoDataFrame]

Dictionary of GeoDataFrames

required x float

x-axis translation. Defaults to 0.

0 y float

y-axis translation. Defaults to 0.

0 scale_x float

x-axis scale. Defaults to 1.

1 scale_y float

y-axis scale. Defaults to 1.

1 rotation float

rotation angle (in radians). Defaults to 0.

0

Returns:

Type Description Dict[str, GeoDataFrame]

Dict[str, gp.GeoDataFrame]: dictionary of transformed GeoDataFrames

Source code in prettymaps/draw.py
@log_execution_time\ndef transform_gdfs(\n    gdfs: Dict[str, gp.GeoDataFrame],\n    x: float = 0,\n    y: float = 0,\n    scale_x: float = 1,\n    scale_y: float = 1,\n    rotation: float = 0,\n    logging=False,\n) -> Dict[str, gp.GeoDataFrame]:\n    \"\"\"\n    Apply geometric transformations to dictionary of GeoDataFrames\n\n    Args:\n        gdfs (Dict[str, gp.GeoDataFrame]): Dictionary of GeoDataFrames\n        x (float, optional): x-axis translation. Defaults to 0.\n        y (float, optional): y-axis translation. Defaults to 0.\n        scale_x (float, optional): x-axis scale. Defaults to 1.\n        scale_y (float, optional): y-axis scale. Defaults to 1.\n        rotation (float, optional): rotation angle (in radians). Defaults to 0.\n\n    Returns:\n        Dict[str, gp.GeoDataFrame]: dictionary of transformed GeoDataFrames\n    \"\"\"\n    # Project geometries\n    gdfs = {\n        name: ox.project_gdf(gdf) if len(gdf) > 0 else gdf for name, gdf in gdfs.items()\n    }\n    # Create geometry collection from gdfs' geometries\n    collection = GeometryCollection(\n        [GeometryCollection(list(gdf.geometry)) for gdf in gdfs.values()]\n    )\n    # Translation, scale & rotation\n    collection = shapely.affinity.translate(collection, x, y)\n    collection = shapely.affinity.scale(collection, scale_x, scale_y)\n    collection = shapely.affinity.rotate(collection, rotation)\n    # Update geometries\n    for i, layer in enumerate(gdfs):\n        gdfs[layer].geometry = list(collection.geoms[i].geoms)\n        # Reproject\n        if len(gdfs[layer]) > 0:\n            gdfs[layer] = ox.project_gdf(gdfs[layer], to_crs=\"EPSG:4326\")\n\n    return gdfs\n
"},{"location":"api/#prettymaps.fetch","title":"prettymaps.fetch","text":"

Prettymaps - A minimal Python library to draw pretty maps from OpenStreetMap Data Copyright (C) 2021 Marcelo Prates

This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.

You should have received a copy of the GNU Affero General Public License along with this program. If not, see https://www.gnu.org/licenses/.

"},{"location":"api/#prettymaps.fetch.get_keypoints","title":"get_keypoints(perimeter, tags={'place': 'quarter', 'highway': True, 'building': True, 'landuse': True, 'natural': True, 'waterway': True, 'amenity': True, 'leisure': True, 'shop': True, 'public_transport': True, 'tourism': True, 'historic': True, 'barrier': True, 'power': True, 'railway': True, 'cycleway': True, 'footway': True, 'healthcare': True, 'office': True, 'craft': True, 'man_made': True, 'boundary': True})","text":"

Extract keypoints from a given perimeter based on specified tags.

Parameters: perimeter (shapely.geometry.Polygon): The polygon representing the area of interest. tags (dict, optional): A dictionary of tags to filter the keypoints. The keys are tag names and the values are booleans indicating whether to include the tag. Default includes a variety of common tags.

Returns: geopandas.GeoDataFrame: A GeoDataFrame containing the keypoints that match the specified tags within the given perimeter.

Source code in prettymaps/fetch.py
def get_keypoints(\n    perimeter,\n    tags={\n        \"place\": \"quarter\",\n        \"highway\": True,\n        \"building\": True,\n        \"landuse\": True,\n        \"natural\": True,\n        \"waterway\": True,\n        \"amenity\": True,\n        \"leisure\": True,\n        \"shop\": True,\n        \"public_transport\": True,\n        \"tourism\": True,\n        \"historic\": True,\n        \"barrier\": True,\n        \"power\": True,\n        \"railway\": True,\n        \"cycleway\": True,\n        \"footway\": True,\n        \"healthcare\": True,\n        \"office\": True,\n        \"craft\": True,\n        \"man_made\": True,\n        \"boundary\": True,\n    },\n):\n    \"\"\"\n    Extract keypoints from a given perimeter based on specified tags.\n\n    Parameters:\n    perimeter (shapely.geometry.Polygon): The polygon representing the area of interest.\n    tags (dict, optional): A dictionary of tags to filter the keypoints. The keys are tag names and the values are booleans indicating whether to include the tag. Default includes a variety of common tags.\n\n    Returns:\n    geopandas.GeoDataFrame: A GeoDataFrame containing the keypoints that match the specified tags within the given perimeter.\n    \"\"\"\n    keypoints_df = ox.features_from_polygon(perimeter, tags=tags)\n\n    return keypoints_df\n
"},{"location":"api/#prettymaps.fetch.merge_tags","title":"merge_tags(layers_dict)","text":"

Merge tags from a dictionary of layers into a single dictionary.

Parameters: layers_dict (dict): Dictionary of layers with their respective tags.

Returns: dict: Merged dictionary of tags.

Source code in prettymaps/fetch.py
def merge_tags(layers_dict: dict) -> dict:\n    \"\"\"\n    Merge tags from a dictionary of layers into a single dictionary.\n\n    Parameters:\n    layers_dict (dict): Dictionary of layers with their respective tags.\n\n    Returns:\n    dict: Merged dictionary of tags.\n    \"\"\"\n\n    layers_dict = deepcopy(layers_dict)\n    merged_tags = {}\n\n    def _merge(d: dict):\n        for key, value in d.items():\n            if isinstance(value, dict):\n                if \"tags\" in value:\n                    for tag_key, tag_value in value[\"tags\"].items():\n                        if tag_key in merged_tags:\n                            if isinstance(merged_tags[tag_key], list):\n                                if isinstance(tag_value, list):\n                                    merged_tags[tag_key].extend(tag_value)\n                                else:\n                                    merged_tags[tag_key].append(tag_value)\n                            else:\n                                merged_tags[tag_key] = (\n                                    [merged_tags[tag_key], tag_value]\n                                    if not isinstance(tag_value, list)\n                                    else [merged_tags[tag_key]] + tag_value\n                                )\n                        else:\n                            merged_tags[tag_key] = (\n                                tag_value\n                                if isinstance(tag_value, list)\n                                else [tag_value]\n                            )\n                _merge(value)\n\n    _merge(layers_dict)\n\n    # Simplify lists with a single element\n    merged_tags = {\n        k: (v[0] if isinstance(v, list) and len(v) == 1 else v)\n        for k, v in merged_tags.items()\n    }\n\n    return merged_tags\n
"},{"location":"api/#prettymaps.fetch.obtain_elevation","title":"obtain_elevation(gdf)","text":"

Download all SRTM elevation tiles for the given polygon in a GeoDataFrame.

Parameters: gdf (GeoDataFrame): GeoDataFrame containing the polygon. output_dir (str): Directory to save the downloaded tiles.

Source code in prettymaps/fetch.py
def obtain_elevation(gdf):\n    \"\"\"\n    Download all SRTM elevation tiles for the given polygon in a GeoDataFrame.\n\n    Parameters:\n    gdf (GeoDataFrame): GeoDataFrame containing the polygon.\n    output_dir (str): Directory to save the downloaded tiles.\n    \"\"\"\n\n    # Ensure the GeoDataFrame has a single polygon\n    if len(gdf) != 1:\n        raise ValueError(\"GeoDataFrame must contain a single polygon\")\n\n    with warnings.catch_warnings():\n        warnings.simplefilter(\"ignore\")\n        subprocess.run(\n            [\"eio\", \"clean\"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL\n        )\n\n    # Get the bounding box of the polygon\n    bounds = gdf.total_bounds\n    min_lon, min_lat, max_lon, max_lat = bounds\n\n    # Configure the bounding box for the elevation library\n\n    output_file = os.path.join(os.getcwd(), \"elevation.tif\")\n    elevation.clip(\n        bounds=(min_lon, min_lat, max_lon, max_lat),\n        output=output_file,\n        margin=\"10%\",\n        cache_dir=\".\",\n    )\n\n    # subprocess.run(\n    #    [\n    #        \"gdalwarp\",\n    #        \"-tr\",\n    #        \"30\",\n    #        \"30\",\n    #        \"-r\",\n    #        \"cubic\",\n    #        \"elevation.tif\",\n    #        \"resampled_elevation.tif\",\n    #    ],\n    #    # stdout=subprocess.DEVNULL,\n    #    # stderr=subprocess.DEVNULL,\n    # )\n\n    raster = rxr.open_rasterio(output_file).squeeze()\n\n    raster = raster.rio.reproject(CRS.from_string(ox.project_gdf(gdf).crs.to_string()))\n\n    # convert to numpy array\n    elevation_data = raster.data\n\n    return elevation_data\n
"},{"location":"api/#prettymaps.fetch.read_from_cache","title":"read_from_cache(perimeter, layer_kwargs, cache_dir='prettymaps_cache')","text":"

Read a GeoDataFrame from the cache based on the perimeter and layer arguments.

Parameters: perimeter (GeoDataFrame): The perimeter GeoDataFrame. layer_kwargs (dict): Dictionary of layer arguments. cache_dir (str): Directory to read the cached GeoDataFrame from.

Returns: GeoDataFrame: The cached GeoDataFrame, or None if it does not exist.

Source code in prettymaps/fetch.py
def read_from_cache(\n    perimeter: GeoDataFrame,\n    layer_kwargs: dict,\n    cache_dir: str = \"prettymaps_cache\",\n) -> GeoDataFrame:\n    \"\"\"\n    Read a GeoDataFrame from the cache based on the perimeter and layer arguments.\n\n    Parameters:\n    perimeter (GeoDataFrame): The perimeter GeoDataFrame.\n    layer_kwargs (dict): Dictionary of layer arguments.\n    cache_dir (str): Directory to read the cached GeoDataFrame from.\n\n    Returns:\n    GeoDataFrame: The cached GeoDataFrame, or None if it does not exist.\n    \"\"\"\n    np.random.seed(0)\n    # Create hash from perimeter\n    perimeter_hash = hash(perimeter[\"geometry\"].to_json())\n    # Create hash from kwargs\n    kwargs_hash = hash(str(layer_kwargs))\n    # Join hashes\n    hash_str = f\"{perimeter_hash}_{kwargs_hash}\"\n\n    cache_path = os.path.join(cache_dir, f\"{hash_str}.geojson\")\n\n    # Check if the gdf is cached\n    if os.path.exists(cache_path):\n        # Read cached gdf\n        return gp.read_file(cache_path)\n    else:\n        return None\n
"},{"location":"api/#prettymaps.fetch.unified_osm_request","title":"unified_osm_request(perimeter, layers_dict, logging=False)","text":"

Unify all OSM requests into one to improve efficiency.

Parameters: perimeter (GeoDataFrame): The perimeter GeoDataFrame. layers_dict (dict): Dictionary of layers to fetch. logging (bool): Enable or disable logging.

Returns: dict: Dictionary of GeoDataFrames for each layer.

Source code in prettymaps/fetch.py
def unified_osm_request(\n    perimeter: GeoDataFrame, layers_dict: dict, logging: bool = False\n) -> dict:\n    \"\"\"\n    Unify all OSM requests into one to improve efficiency.\n\n    Parameters:\n    perimeter (GeoDataFrame): The perimeter GeoDataFrame.\n    layers_dict (dict): Dictionary of layers to fetch.\n    logging (bool): Enable or disable logging.\n\n    Returns:\n    dict: Dictionary of GeoDataFrames for each layer.\n    \"\"\"\n    # Apply tolerance to the perimeter\n    perimeter_with_tolerance = ox.project_gdf(perimeter).buffer(0).to_crs(4326)\n    perimeter_with_tolerance = unary_union(perimeter_with_tolerance.geometry).buffer(0)\n\n    # Fetch from perimeter's bounding box, to avoid missing some geometries\n    bbox = box(*perimeter_with_tolerance.bounds)\n\n    # Initialize the result dictionary\n    gdfs = {}\n    ## Read layers from cache\n    # for layer, kwargs in layers_dict.items():\n    #    gdf = read_from_cache(perimeter, layers_dict[layer])\n    #    if gdf is not None:\n    #        gdfs[layer] = gdf\n\n    # Combine all tags into a single dictionary for a unified request\n    combined_tags = merge_tags(\n        {layer: kwargs for layer, kwargs in layers_dict.items() if layer not in gdfs}\n    )\n\n    # Fetch all features in one request\n    try:\n        all_features = ox.features_from_polygon(bbox, tags=combined_tags)\n    except Exception as e:\n        all_features = GeoDataFrame(geometry=[])\n\n    # Split the features into separate GeoDataFrames based on the layers_dict\n    for layer, kwargs in layers_dict.items():\n        if layer in gdfs:\n            continue\n        try:\n            if layer in [\"streets\", \"railway\", \"waterway\"]:\n                graph = ox.graph_from_polygon(\n                    bbox,\n                    custom_filter=kwargs.get(\"custom_filter\"),\n                    truncate_by_edge=True,\n                )\n                gdf = ox.graph_to_gdfs(graph, nodes=False)\n            elif layer == \"sea\":\n                try:\n                    coastline = unary_union(\n                        ox.features_from_polygon(\n                            bbox, tags={\"natural\": \"coastline\"}\n                        ).geometry.tolist()\n                    )\n                    sea_candidates = bbox.difference(coastline.buffer(1e-9)).geoms\n                    drive = ox.graph_from_polygon(bbox, network_type=\"drive\")\n                    drive = ox.graph_to_gdfs(drive, nodes=False)\n\n                    def filter_candidate(sea_candidate):\n                        intersections = drive.geometry.intersects(sea_candidate)\n                        if \"bridge\" in drive.columns:\n                            return not any(\n                                intersections\n                                & (\n                                    drive.loc[\n                                        drive.geometry.intersects(sea_candidate),\n                                        \"bridge\",\n                                    ]\n                                    != \"yes\"\n                                )\n                            )\n                        else:\n                            return not any(intersections)\n\n                    sea = unary_union(\n                        MultiPolygon(\n                            [\n                                candidate\n                                for candidate in sea_candidates\n                                if filter_candidate(candidate)\n                            ]\n                        ).geoms\n                    ).buffer(1e-8)\n                    gdf = GeoDataFrame(geometry=[sea], crs=perimeter.crs)\n                except:\n                    gdf = GeoDataFrame(geometry=[], crs=perimeter.crs)\n            else:\n                if kwargs.get(\"osmid\") is None:\n                    if layer == \"perimeter\":\n                        gdf = perimeter\n                    else:\n                        layer_tags = kwargs.get(\"tags\")\n                        gdf = gp.GeoDataFrame(geometry=[], crs=perimeter.crs)\n                        for key, value in layer_tags.items():\n                            if isinstance(value, bool) and value:\n                                filtered_features = all_features[\n                                    ~pd.isna(all_features[key])\n                                ]\n                            elif isinstance(value, list):\n                                filtered_features = all_features[\n                                    all_features[key].isin(value)\n                                ]\n                            else:\n                                filtered_features = all_features[\n                                    all_features[key] == value\n                                ]\n                            gdf = pd.concat([gdf, filtered_features], axis=0)\n                else:\n                    gdf = ox.geocode_to_gdf(kwargs.get(\"osmid\"), by_osmid=True)\n\n            gdf = gdf.copy()\n            gdf.geometry = gdf.geometry.intersection(perimeter_with_tolerance)\n            gdf.drop(gdf[gdf.geometry.is_empty].index, inplace=True)\n            gdfs[layer] = gdf\n            # write_to_cache(perimeter, gdf, layers_dict[layer])\n        except Exception as e:\n            # print(f\"Error fetching {layer}: {e}\")\n            gdfs[layer] = GeoDataFrame(geometry=[])\n\n    return gdfs\n
"},{"location":"api/#prettymaps.fetch.write_to_cache","title":"write_to_cache(perimeter, gdf, layer_kwargs, cache_dir='prettymaps_cache')","text":"

Write a GeoDataFrame to the cache based on the perimeter and layer arguments.

Parameters: perimeter (GeoDataFrame): The perimeter GeoDataFrame. gdf (GeoDataFrame): The GeoDataFrame to cache. layer_kwargs (dict): Dictionary of layer arguments. cache_dir (str): Directory to save the cached GeoDataFrame.

Source code in prettymaps/fetch.py
def write_to_cache(\n    perimeter: GeoDataFrame,\n    gdf: GeoDataFrame,\n    layer_kwargs: dict,\n    cache_dir: str = \"prettymaps_cache\",\n):\n    \"\"\"\n    Write a GeoDataFrame to the cache based on the perimeter and layer arguments.\n\n    Parameters:\n    perimeter (GeoDataFrame): The perimeter GeoDataFrame.\n    gdf (GeoDataFrame): The GeoDataFrame to cache.\n    layer_kwargs (dict): Dictionary of layer arguments.\n    cache_dir (str): Directory to save the cached GeoDataFrame.\n    \"\"\"\n    np.random.seed(0)\n    os.makedirs(cache_dir, exist_ok=True)\n\n    # Create hash from perimeter\n    perimeter_hash = hash(perimeter[\"geometry\"].to_json())\n    # Create hash from kwargs\n    kwargs_hash = hash(str(layer_kwargs))\n    # Join hashes\n    hash_str = f\"{perimeter_hash}_{kwargs_hash}\"\n\n    cache_path = os.path.join(cache_dir, f\"{hash_str}.geojson\")\n\n    # Write gdf to cache\n    logging.getLogger().setLevel(logging.CRITICAL)\n    if not gdf.empty:\n        gdf.to_file(cache_path, driver=\"GeoJSON\")\n    logging.getLogger().setLevel(logging.INFO)\n
"},{"location":"usage/","title":"Usage","text":""},{"location":"usage/#installation","title":"Installation","text":"

Install Prettymaps and its dependencies:

pip install prettymaps\n
"},{"location":"usage/#basic-usage","title":"Basic Usage","text":"

Generate a map for a location:

import prettymaps\n\nprettymaps.plot(\"Paris, France\")\n
"},{"location":"usage/#customizing-layers-and-styles","title":"Customizing Layers and Styles","text":"

You can customize which map layers to show and their appearance:

layers = {\n    \"perimeter\": {},\n    \"streets\": {\"width\": 8},\n    \"buildings\": {},\n    \"water\": {},\n}\nstyle = {\n    \"perimeter\": {\"fc\": \"#f2efe9\", \"ec\": \"#333\"},\n    \"streets\": {\"fc\": \"#cccccc\"},\n    \"buildings\": {\"fc\": \"#b0b0b0\"},\n    \"water\": {\"fc\": \"#aadaff\"},\n}\nprettymaps.plot(\"Berlin, Germany\", layers=layers, style=style)\n
"},{"location":"usage/#saving-maps","title":"Saving Maps","text":"

You can save the generated map to a file:

prettymaps.plot(\"Tokyo, Japan\", save_as=\"tokyo_map.png\")\n
"},{"location":"usage/#advanced-features","title":"Advanced Features","text":"
  • Keypoints: Highlight specific places or features on the map.
  • Presets: Save and reuse your favorite map styles.
  • Hillshade: Add elevation shading for a 3D effect.

See the API Reference for all available options.

"}]} \ No newline at end of file diff --git a/usage/index.html b/usage/index.html index f6ee42f..cb94445 100644 --- a/usage/index.html +++ b/usage/index.html @@ -237,6 +237,19 @@ + + @@ -249,6 +262,70 @@ + + + + @@ -295,6 +372,59 @@ + + + @@ -310,8 +440,43 @@

Usage

-

This page will describe how to use Prettymaps.

-

More details coming soon!

+

Installation

+

Install Prettymaps and its dependencies:

+
pip install prettymaps
+
+

Basic Usage

+

Generate a map for a location:

+
import prettymaps
+
+prettymaps.plot("Paris, France")
+
+

Customizing Layers and Styles

+

You can customize which map layers to show and their appearance:

+
layers = {
+    "perimeter": {},
+    "streets": {"width": 8},
+    "buildings": {},
+    "water": {},
+}
+style = {
+    "perimeter": {"fc": "#f2efe9", "ec": "#333"},
+    "streets": {"fc": "#cccccc"},
+    "buildings": {"fc": "#b0b0b0"},
+    "water": {"fc": "#aadaff"},
+}
+prettymaps.plot("Berlin, Germany", layers=layers, style=style)
+
+

Saving Maps

+

You can save the generated map to a file:

+
prettymaps.plot("Tokyo, Japan", save_as="tokyo_map.png")
+
+

Advanced Features

+
    +
  • Keypoints: Highlight specific places or features on the map.
  • +
  • Presets: Save and reuse your favorite map styles.
  • +
  • Hillshade: Add elevation shading for a 3D effect.
  • +
+

See the API Reference for all available options.