# prettymaps A minimal Python library to draw customized maps from [OpenStreetMap](https://www.openstreetmap.org/#map=12/11.0733/106.3078) created using the [osmnx](https://github.com/gboeing/osmnx), [matplotlib](https://matplotlib.org/), [shapely](https://shapely.readthedocs.io/en/stable/index.html) and [vsketch](https://github.com/abey79/vsketch) packages. ![](https://github.com/marceloprates/prettymaps/raw/main/prints/heerhugowaard.png) This work is [licensed](LICENSE) under a GNU Affero General Public License v3.0 (you can make commercial use, distribute and modify this project, but must **disclose** the source code with the license and copyright notice) ## Note about crediting and NFTs: - Please keep the printed message on the figures crediting my repository and OpenStreetMap ([mandatory by their license](https://www.openstreetmap.org/copyright)). - I am personally **against** NFTs for their [environmental impact](https://earth.org/nfts-environmental-impact/), the fact that they're a [giant money-laundering pyramid scheme](https://twitter.com/smdiehl/status/1445795667826208770) and the structural incentives they create for [theft](https://twitter.com/NFTtheft) in the open source and generative art communities. - **I do not authorize in any way this project to be used for selling NFTs**, although I cannot legally enforce it. **Respect the creator**. - The [AeternaCivitas](https://magiceden.io/marketplace/aeterna_civitas) and [geoartnft](https://www.geo-nft.com/) projects have used this work to sell NFTs and refused to credit it. See how they reacted after being exposed: [AeternaCivitas](https://github.com/marceloprates/prettymaps/raw/main/pictures/NFT_theft_AeternaCivitas.jpg), [geoartnft](https://github.com/marceloprates/prettymaps/raw/main/pictures/NFT_theft_geoart.jpg). - **I have closed my other generative art projects on Github and won't be sharing new ones as open source to protect me from the NFT community**. Buy Me a Coffee at ko-fi.com ## As seen on [Hacker News](https://web.archive.org/web/20210825160918/https://news.ycombinator.com/news): ![](https://github.com/marceloprates/prettymaps/raw/main/prints/hackernews-prettymaps.png) ## [prettymaps subreddit](https://www.reddit.com/r/prettymaps_/) ## [Google Colaboratory Demo](https://colab.research.google.com/github/marceloprates/prettymaps/blob/master/notebooks/examples.ipynb) # Installation ### Install locally: Install prettymaps with: ``` pip install prettymaps ``` ### Install on Google Colaboratory: Install prettymaps with: ``` !pip install -e "git+https://github.com/marceloprates/prettymaps#egg=prettymaps" ``` Then **restart the runtime** (Runtime -> Restart Runtime) before importing prettymaps # Run front-end After prettymaps is installed, you can run the front-end (streamlit) application from the prettymaps repository using: ``` streamlit run app.py ``` # Tutorial Plotting with prettymaps is very simple. Run: ```python prettymaps.plot(your_query) ``` **your_query** can be: 1. An address (Example: "Porto Alegre"), 2. Latitude / Longitude coordinates (Example: (-30.0324999, -51.2303767)) 3. A custom boundary in GeoDataFrame format ```python %reload_ext autoreload %autoreload 2 import prettymaps plot = prettymaps.plot('Stad van de Zon, Heerhugowaard, Netherlands') ``` Fetching geodataframes took 2.04 seconds ![png](pictures/README/temp_readme_files/temp_readme_7_1.png) You can also choose from different "presets" (parameter combinations saved in JSON files) See below an example using the "minimal" preset ```python import prettymaps plot = prettymaps.plot( 'Stad van de Zon, Heerhugowaard, Netherlands', preset = 'minimal' ) ``` Fetching geodataframes took 0.81 seconds ![png](pictures/README/temp_readme_files/temp_readme_9_1.png) Run ```python prettymaps.presets() ``` to list all available presets: ```python import prettymaps prettymaps.presets() ```
preset params
0 abraca-redencao {'layers': {'perimeter': {}, 'streets': {'widt...
1 barcelona {'layers': {'perimeter': {'circle': False}, 's...
2 barcelona-plotter {'layers': {'streets': {'width': {'primary': 5...
3 cb-bf-f {'layers': {'streets': {'width': {'trunk': 6, ...
4 default {'layers': {'perimeter': {}, 'streets': {'widt...
5 heerhugowaard {'layers': {'perimeter': {}, 'streets': {'widt...
6 macao {'layers': {'perimeter': {}, 'streets': {'cust...
7 minimal {'layers': {'perimeter': {}, 'streets': {'widt...
8 my-preset {'layers': {'building': {'tags': {'building': ...
9 plotter {'layers': {'perimeter': {}, 'streets': {'widt...
10 pytest-temp-preset {'layers': {'building': {'tags': {'building': ...
11 tijuca {'layers': {'perimeter': {}, 'streets': {'widt...
To examine a specific preset, run: ```python import prettymaps prettymaps.preset('default') ``` Preset(params={'layers': {'perimeter': {}, 'streets': {'width': {'motorway': 5, 'trunk': 5, 'primary': 4.5, 'secondary': 4, 'tertiary': 3.5, 'cycleway': 3.5, 'residential': 3, 'service': 2, 'unclassified': 2, 'pedestrian': 2, 'footway': 1}}, 'waterway': {'tags': {'waterway': ['river', 'stream']}, 'width': {'river': 20, 'stream': 10}}, 'building': {'tags': {'building': True, 'landuse': 'construction'}}, 'water': {'tags': {'natural': ['water', 'bay']}}, 'sea': {}, 'forest': {'tags': {'landuse': 'forest'}}, 'green': {'tags': {'landuse': ['grass', 'orchard'], 'natural': ['island', 'wood', 'wetland'], 'leisure': 'park'}}, 'rock': {'tags': {'natural': 'bare_rock'}}, 'beach': {'tags': {'natural': 'beach'}}, 'parking': {'tags': {'amenity': 'parking', 'highway': 'pedestrian', 'man_made': 'pier'}}}, 'style': {'perimeter': {'fill': False, 'lw': 0, 'zorder': 0}, 'background': {'fc': '#F2F4CB', 'zorder': -1}, 'green': {'fc': '#8BB174', 'ec': '#2F3737', 'hatch_c': '#A7C497', 'hatch': 'ooo...', 'lw': 1, 'zorder': 1}, 'forest': {'fc': '#64B96A', 'ec': '#2F3737', 'lw': 1, 'zorder': 2}, 'water': {'fc': '#a8e1e6', 'ec': '#2F3737', 'hatch_c': '#9bc3d4', 'hatch': 'ooo...', 'lw': 1, 'zorder': 99}, 'sea': {'fc': '#a8e1e6', 'ec': '#2F3737', 'hatch_c': '#9bc3d4', 'hatch': 'ooo...', 'lw': 1, 'zorder': 99}, 'waterway': {'fc': '#a8e1e6', 'ec': '#2F3737', 'hatch_c': '#9bc3d4', 'hatch': 'ooo...', 'lw': 1, 'zorder': 200}, 'beach': {'fc': '#FCE19C', 'ec': '#2F3737', 'hatch_c': '#d4d196', 'hatch': 'ooo...', 'lw': 1, 'zorder': 3}, 'parking': {'fc': '#F2F4CB', 'ec': '#2F3737', 'lw': 1, 'zorder': 3}, 'streets': {'fc': '#2F3737', 'ec': '#475657', 'alpha': 1, 'lw': 0, 'zorder': 4}, 'building': {'palette': ['#433633', '#FF5E5B'], 'ec': '#2F3737', 'lw': 0.5, 'zorder': 5}, 'rock': {'fc': '#BDC0BA', 'ec': '#2F3737', 'lw': 1, 'zorder': 6}}, 'circle': None, 'radius': 500}) Insted of using the default configuration you can customize several parameters. The most important are: - layers: A dictionary of OpenStreetMap layers to fetch. - Keys: layer names (arbitrary) - Values: dicts representing OpenStreetMap queries - style: Matplotlib style parameters - Keys: layer names (the same as before) - Values: dicts representing Matplotlib style parameters ```python plot = prettymaps.plot( # Your query. Example: "Porto Alegre" or (-30.0324999, -51.2303767) (GPS coords) your_query, # Dict of OpenStreetMap Layers to plot. Example: # {'building': {'tags': {'building': True}}, 'water': {'tags': {'natural': 'water'}}} # Check the /presets folder for more examples layers, # Dict of style parameters for matplotlib. Example: # {'building': {'palette': ['#f00','#0f0','#00f'], 'edge_color': '#333'}} style, # Preset to load. Options include: # ['default', 'minimal', 'macao', 'tijuca'] preset, # Save current parameters to a preset file. # Example: "my-preset" will save to "presets/my-preset.json" save_preset, # Whether to update loaded preset with additional provided parameters. Boolean update_preset, # Plot with circular boundary. Boolean circle, # Plot area radius. Float radius, # Dilate the boundary by this amount. Float dilate ) ``` **plot** is a python dataclass containing: ```python @dataclass class Plot: # A dictionary of GeoDataFrames (one for each plot layer) geodataframes: Dict[str, gp.GeoDataFrame] # A matplotlib figure fig: matplotlib.figure.Figure # A matplotlib axis object ax: matplotlib.axes.Axes ``` Here's an example of running prettymaps.plot() with customized parameters: ```python import prettymaps plot = prettymaps.plot( 'Praça Ferreira do Amaral, Macau', circle = True, radius = 1100, layers = { "green": { "tags": { "landuse": "grass", "natural": ["island", "wood"], "leisure": "park" } }, "forest": { "tags": { "landuse": "forest" } }, "water": { "tags": { "natural": ["water", "bay"] } }, "parking": { "tags": { "amenity": "parking", "highway": "pedestrian", "man_made": "pier" } }, "streets": { "width": { "motorway": 5, "trunk": 5, "primary": 4.5, "secondary": 4, "tertiary": 3.5, "residential": 3, } }, "building": { "tags": {"building": True}, }, }, style = { "background": { "fc": "#F2F4CB", "ec": "#dadbc1", "hatch": "ooo...", }, "perimeter": { "fc": "#F2F4CB", "ec": "#dadbc1", "lw": 0, "hatch": "ooo...", }, "green": { "fc": "#D0F1BF", "ec": "#2F3737", "lw": 1, }, "forest": { "fc": "#64B96A", "ec": "#2F3737", "lw": 1, }, "water": { "fc": "#a1e3ff", "ec": "#2F3737", "hatch": "ooo...", "hatch_c": "#85c9e6", "lw": 1, }, "parking": { "fc": "#F2F4CB", "ec": "#2F3737", "lw": 1, }, "streets": { "fc": "#2F3737", "ec": "#475657", "alpha": 1, "lw": 0, }, "building": { "palette": [ "#FFC857", "#E9724C", "#C5283D" ], "ec": "#2F3737", "lw": 0.5, } } ) ``` Fetching geodataframes took 15.97 seconds ![png](pictures/README/temp_readme_files/temp_readme_15_1.png) In order to plot an entire region and not just a rectangular or circular area, set ```python radius = False ``` ```python import prettymaps plot = prettymaps.plot( 'Bom Fim, Porto Alegre, Brasil', radius = False, ) ``` Fetching geodataframes took 1.28 seconds ![png](pictures/README/temp_readme_files/temp_readme_17_1.png) You can access layers's GeoDataFrames directly like this: ```python import prettymaps # Run prettymaps in show = False mode (we're only interested in obtaining the GeoDataFrames) plot = prettymaps.plot('Centro Histórico, Porto Alegre', show = False) plot.geodataframes['building'] ``` Fetching geodataframes took 1.59 seconds
geometry bicycle highway leisure addr:housenumber addr:street amenity operator website historic ... contact:website bus smoothness inscription ways boat name:fr type building:part architect
(node, 2407915698) POINT (-51.23212 -30.03670) NaN NaN NaN 820 Rua Washington Luiz NaN NaN NaN NaN ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
(way, 126665330) POLYGON ((-51.23518 -30.03275, -51.23512 -30.0... NaN NaN NaN 387 Rua dos Andradas place_of_worship NaN NaN NaN ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
(way, 126665331) POLYGON ((-51.23167 -30.03066, -51.23160 -30.0... NaN NaN NaN 1001 Rua dos Andradas NaN NaN https://www.ruadapraiashopping.com.br/ NaN ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
(way, 129176990) POLYGON ((-51.23117 -30.02891, -51.23120 -30.0... NaN NaN NaN 1020 Rua 7 de Setembro NaN NaN http://www.memorial.rs.gov.br NaN ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
(way, 129176991) POLYGON ((-51.23153 -30.02914, -51.23156 -30.0... NaN NaN NaN NaN Praça da Alfândega NaN NaN https://www.margs.rs.gov.br/ NaN ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
(relation, 6760281) POLYGON ((-51.23238 -30.03337, -51.23223 -30.0... NaN NaN NaN NaN NaN NaN NaN NaN NaN ... NaN NaN NaN NaN [457506887, 457506886] NaN NaN multipolygon NaN NaN
(relation, 6760282) POLYGON ((-51.23203 -30.03340, -51.23203 -30.0... NaN NaN NaN NaN NaN NaN NaN NaN NaN ... NaN NaN NaN NaN [457506875, 457506889, 457506888] NaN NaN multipolygon NaN NaN
(relation, 6760283) POLYGON ((-51.23284 -30.03367, -51.23288 -30.0... NaN NaN NaN NaN NaN NaN NaN NaN NaN ... NaN NaN NaN NaN [457506897, 457506896] NaN NaN multipolygon NaN Theodor Wiederspahn
(relation, 6760284) POLYGON ((-51.23499 -30.03412, -51.23498 -30.0... NaN NaN NaN NaN NaN NaN NaN NaN NaN ... NaN NaN NaN NaN [457506910, 457506913] NaN NaN multipolygon NaN NaN
(relation, 14393526) POLYGON ((-51.23125 -30.02813, -51.23128 -30.0... NaN NaN NaN 1044 Rua Siqueira Campos NaN NaN https://www.sefaz.rs.gov.br NaN ... NaN NaN NaN NaN [236213286, 1081974882] NaN NaN multipolygon NaN NaN

2420 rows × 167 columns

Search a building by name and display it: ```python plot.geodataframes['building'][ plot.geodataframes['building'].name == 'Catedral Metropolitana Nossa Senhora Mãe de Deus' ].geometry[0] ``` /opt/anaconda3/envs/prettymaps/lib/python3.11/site-packages/geopandas/geoseries.py:648: FutureWarning: Series.__getitem__ treating keys as positions is deprecated. In a future version, integer keys will always be treated as labels (consistent with DataFrame behavior). To access a value by position, use `ser.iloc[pos]` val = getattr(super(), mtd)(*args, **kwargs) ![svg](pictures/README/temp_readme_files/temp_readme_21_1.svg) Plot mosaic of building footprints ```python import prettymaps import numpy as np import osmnx as ox from matplotlib import pyplot as plt # Run prettymaps in show = False mode (we're only interested in obtaining the GeoDataFrames) plot = prettymaps.plot('Porto Alegre', show = False) # Get list of buildings from plot's geodataframes dict buildings = plot.geodataframes['building'] # Project from lat / long buildings = ox.project_gdf(buildings) buildings = [b for b in buildings.geometry if b.area > 0] # Draw Matplotlib mosaic of n x n building footprints n = 6 fig,axes = plt.subplots(n,n, figsize = (7,6)) # Set background color fig.patch.set_facecolor('#5cc0eb') # Figure title fig.suptitle( 'Buildings of Porto Alegre', size = 25, color = '#fff' ) # Draw each building footprint on a separate axis for ax,building in zip(np.concatenate(axes),buildings): ax.plot(*building.exterior.xy, c = '#ffffff') ax.autoscale(); ax.axis('off'); ax.axis('equal') ``` Fetching geodataframes took 2.01 seconds ![png](pictures/README/temp_readme_files/temp_readme_23_1.png) Access plot.ax or plot.fig to add new elements to the matplotlib plot: ```python import prettymaps plot = prettymaps.plot( (41.39491,2.17557), preset = 'barcelona', show = False # We don't want to render the map yet ) # Change background color plot.fig.patch.set_facecolor('#F2F4CB') # Add title _ = plot.ax.set_title( 'Barcelona', font = 'serif', size = 50 ) ``` Fetching geodataframes took 3.78 seconds Use **plotter** mode to export a pen plotter-compatible SVG (thanks to abey79's amazing [vsketch](https://github.com/abey79/vsketch) library) ```python import prettymaps plot = prettymaps.plot( (41.39491,2.17557), mode = 'plotter', layers = dict(perimeter = {}), preset = 'barcelona-plotter', scale_x = .6, scale_y = -.6, ) ``` Fetching geodataframes took 3.89 seconds ![png](pictures/README/temp_readme_files/temp_readme_27_1.png) Some other examples ```python import prettymaps plot = prettymaps.plot( 'Barra da Tijuca', dilate = 0, figsize = (22,10), preset = 'tijuca', adjust_aspect_ratio = False ) ``` Fetching geodataframes took 16.55 seconds ![png](pictures/README/temp_readme_files/temp_readme_29_1.png) Use prettymaps.create_preset() to create a preset: ```python import prettymaps prettymaps.create_preset( "my-preset", layers = { "building": { "tags": { "building": True, "leisure": [ "track", "pitch" ] } }, "streets": { "width": { "trunk": 6, "primary": 6, "secondary": 5, "tertiary": 4, "residential": 3.5, "pedestrian": 3, "footway": 3, "path": 3 } }, }, style = { "perimeter": { "fill": False, "lw": 0, "zorder": 0 }, "streets": { "fc": "#F1E6D0", "ec": "#2F3737", "lw": 1.5, "zorder": 3 }, "building": { "palette": [ "#fff" ], "ec": "#2F3737", "lw": 1, "zorder": 4 } } ) prettymaps.preset('my-preset') ``` Preset(params={'layers': {'building': {'tags': {'building': True, 'leisure': ['track', 'pitch']}}, 'streets': {'width': {'trunk': 6, 'primary': 6, 'secondary': 5, 'tertiary': 4, 'residential': 3.5, 'pedestrian': 3, 'footway': 3, 'path': 3}}}, 'style': {'perimeter': {'fill': False, 'lw': 0, 'zorder': 0}, 'streets': {'fc': '#F1E6D0', 'ec': '#2F3737', 'lw': 1.5, 'zorder': 3}, 'building': {'palette': ['#fff'], 'ec': '#2F3737', 'lw': 1, 'zorder': 4}}, 'circle': None, 'radius': None, 'dilate': None}) Use **prettymaps.multiplot** and **prettymaps.Subplot** to draw multiple regions on the same canvas ```python import prettymaps # Draw several regions on the same canvas plot = prettymaps.multiplot( prettymaps.Subplot( 'Cidade Baixa, Porto Alegre', style={'building': {'palette': ['#49392C', '#E1F2FE', '#98D2EB']}} ), prettymaps.Subplot( 'Bom Fim, Porto Alegre', style={'building': {'palette': ['#BA2D0B', '#D5F2E3', '#73BA9B', '#F79D5C']}} ), prettymaps.Subplot( 'Farroupilha, Porto Alegre', layers = {'building': {'tags': {'building': True}}}, style={'building': {'palette': ['#EEE4E1', '#E7D8C9', '#E6BEAE']}} ), # Load a global preset preset='cb-bf-f', # Figure size figsize=(12, 12) ) ``` Fetching geodataframes took 0.97 seconds Fetching geodataframes took 1.15 seconds Fetching geodataframes took 0.79 seconds ![png](pictures/README/temp_readme_files/temp_readme_33_1.png) # Add hillshade ```python plot = prettymaps.plot( 'Honolulu', radius = 5500, figsize = 'a4', layers = {'hillshade': { 'azdeg': 315, 'altdeg': 45, 'vert_exag': 1, 'dx': 1, 'dy': 1, 'alpha': 0.75, }}, ) ``` Fetching geodataframes took 24.92 seconds curl -s -o spool/N21/N21W158.hgt.gz.temp https://s3.amazonaws.com/elevation-tiles-prod/skadi/N21/N21W158.hgt.gz && mv spool/N21/N21W158.hgt.gz.temp spool/N21/N21W158.hgt.gz gunzip spool/N21/N21W158.hgt.gz 2>/dev/null || touch spool/N21/N21W158.hgt gdal_translate -q -co TILED=YES -co COMPRESS=DEFLATE -co ZLEVEL=9 -co PREDICTOR=2 spool/N21/N21W158.hgt cache/N21/N21W158.tif 2>/dev/null || touch cache/N21/N21W158.tif rm spool/N21/N21W158.hgt gdalbuildvrt -q -overwrite SRTM1.vrt cache/N21/N21W158.tif cp SRTM1.vrt SRTM1.5af18c5270144c688522a27abf3b23a0.vrt gdal_translate -q -co TILED=YES -co COMPRESS=DEFLATE -co ZLEVEL=9 -co PREDICTOR=2 -projwin -157.90125854957773 21.364471426268274 -157.81006761682832 21.244615177105377 SRTM1.5af18c5270144c688522a27abf3b23a0.vrt /Users/marceloprates/Projects/Art/07_Data_Visualization_and_Maps/prettymaps/notebooks/elevation.tif rm -f SRTM1.5af18c5270144c688522a27abf3b23a0.vrt WARNING:matplotlib.axes._base:Ignoring fixed y limits to fulfill fixed data aspect with adjustable data limits. ![png](pictures/README/temp_readme_files/temp_readme_35_2.png) # Add keypoints ```python plot = prettymaps.plot( 'Garopaba', radius = 5000, figsize = 'a4', layers = {'building': False}, keypoints = { # Search for general keypoints specified by OSM tags 'tags': {'natural': ['beach']}, # Or, search by specific name or free-text search # pretymaps will use a fuzzy string matching to search for the specified name 'specific': { 'pedra branca': {'tags': {'natural': ['peak']}}, } }, ) ``` Fetching geodataframes took 21.47 seconds ![png](pictures/README/temp_readme_files/temp_readme_37_1.png)