Reorganized repo structure, added github workflows

This commit is contained in:
Marcelo Prates
2025-05-16 20:09:31 -03:00
parent eaee9849ae
commit ec0ef4db78
87 changed files with 917 additions and 113 deletions

0
.devcontainer/devcontainer.json Normal file → Executable file
View File

0
.github/FUNDING.yml vendored Normal file → Executable file
View File

27
.github/workflows/docs.yml vendored Normal file
View File

@@ -0,0 +1,27 @@
name: Deploy Docs
on:
push:
branches:
- main
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install dependencies
run: |
pip install -r docs/requirements.txt
- name: Build docs
run: |
mkdocs build
- name: Deploy to GitHub Pages
uses: peaceiris/actions-gh-pages@v4
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./site

55
.github/workflows/generate-readme.yml vendored Normal file
View File

@@ -0,0 +1,55 @@
name: Generate README
on:
push:
branches: ["**"]
pull_request:
branches: ["**"]
jobs:
generate-readme:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install nbconvert
run: |
pip install nbconvert
- name: Install jupyter
run: |
pip install jupyter
- name: Install package
run: |
pip install -e .
- name: Execute the notebook (run all cells)
run: |
jupyter nbconvert --to notebook --execute notebooks/examples.ipynb --output notebooks/examples.ipynb
- name: Run notebooks_to_readme.sh
run: |
bash scripts/notebooks_to_readme.sh
- name: Git add generated files
run: |
git add README.md notebooks/examples.ipynb pictures/README/*.png
- name: Commit changes
run: |
git config --global user.name "github-actions[bot]"
git config --global user.email "github-actions[bot]@users.noreply.github.com"
git commit -m "Update README, notebook, and images from CI" || echo "No changes to commit"
- name: Push changes
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
git push origin HEAD:${GITHUB_REF#refs/heads/}

0
.github/workflows/publish-package.yml vendored Normal file → Executable file
View File

44
.github/workflows/test.yml vendored Normal file
View File

@@ -0,0 +1,44 @@
name: Run Tests
on:
push:
branches: ["**"]
pull_request:
branches: ["**"]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.10"]
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Cache pip
uses: actions/cache@v4
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
restore-keys: |
${{ runner.os }}-pip-
- name: Install package and dependencies
run: |
pip install -e .
pip install pytest
- name: Run tests with pytest
run: pytest --junitxml=pytest-results.xml tests
- name: Upload pytest results
uses: actions/upload-artifact@v4
with:
name: pytest-results
path: pytest-results.xml

50
.gitignore vendored Normal file → Executable file
View File

@@ -1,3 +1,49 @@
# Byte-compiled / optimized / DLL files
*.py[cod]
*.egg-info
cache
__pycache__/
# Distribution / packaging
*.egg-info/
build/
dist/
prettymaps.egg-info/
# Caches and logs
cache/
outputs/
.DS_Store
*.log
*.bak
*.dat
*.sqlite
# Data
/data/
# Ignore all notebooks except examples.ipynb
notebooks/*
!notebooks/examples.ipynb
# Exclude etc folder
etc/
# Documentation images and archives
/docs/images/
/docs/archive/
# Scripts (if you want to ignore generated or temp files in scripts)
/scripts/*.pyc
/scripts/__pycache__/
# Ignore SRTM1 data
SRTM1/
# Ignore OS and editor files
*.swp
*~
.vscode/
.devcontainer/
# Ignore test and temp files
*.tmp
*.tmp.png

0
LICENSE Normal file → Executable file
View File

0
MANIFEST.in Normal file → Executable file
View File

103
README.md
View File

@@ -2,13 +2,8 @@
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.
# Prettymaps is now available as a streamlit app!
![](https://github.com/marceloprates/prettymaps/raw/main/prints/heerhugowaard.png)
[![Streamlit App](https://img.shields.io/badge/Streamlit-Live-blue?logo=streamlit)](https://prettymaps.streamlit.app/)
[![image](https://github.com/user-attachments/assets/14e56496-9eab-4b31-ad05-6227d56cfbd2)](https://prettymaps.streamlit.app/)
<!--![](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)
@@ -16,7 +11,7 @@ This work is [licensed](LICENSE) under a GNU Affero General Public License v3.0
- 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](etc/NFT_theft_AeternaCivitas.jpg), [geoartnft](etc/NFT_theft_geoart.jpg).
- 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**.
<a href='https://ko-fi.com/marceloprates_' target='_blank'><img height='36' style='border:0px;height:36px;' src='https://cdn.ko-fi.com/cdn/kofi1.png?v=3' border='0' alt='Buy Me a Coffee at ko-fi.com' /></a>
@@ -24,12 +19,6 @@ This work is [licensed](LICENSE) under a GNU Affero General Public License v3.0
## 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)
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=marceloprates/prettymaps&type=Date&theme=dark" />
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=marceloprates/prettymaps&type=Date" />
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=star-history/star-history&type=Date" />
</picture>
## [prettymaps subreddit](https://www.reddit.com/r/prettymaps_/)
## [Google Colaboratory Demo](https://colab.research.google.com/github/marceloprates/prettymaps/blob/master/notebooks/examples.ipynb)
@@ -81,9 +70,12 @@ import prettymaps
plot = prettymaps.plot('Stad van de Zon, Heerhugowaard, Netherlands')
```
Fetching geodataframes took 2.04 seconds
![png](README_files/README_7_0.png)
![png](pictures/README/temp_readme_files/temp_readme_7_1.png)
@@ -101,9 +93,12 @@ plot = prettymaps.plot(
)
```
Fetching geodataframes took 0.81 seconds
![png](README_files/README_9_0.png)
![png](pictures/README/temp_readme_files/temp_readme_9_1.png)
@@ -200,6 +195,11 @@ prettymaps.presets()
</tr>
<tr>
<th>10</th>
<td>pytest-temp-preset</td>
<td>{'layers': {'building': {'tags': {'building': ...</td>
</tr>
<tr>
<th>11</th>
<td>tijuca</td>
<td>{'layers': {'perimeter': {}, 'streets': {'widt...</td>
</tr>
@@ -378,9 +378,12 @@ plot = prettymaps.plot(
)
```
Fetching geodataframes took 15.97 seconds
![png](README_files/README_15_0.png)
![png](pictures/README/temp_readme_files/temp_readme_15_1.png)
@@ -399,9 +402,12 @@ plot = prettymaps.plot(
)
```
Fetching geodataframes took 1.28 seconds
![png](README_files/README_17_0.png)
![png](pictures/README/temp_readme_files/temp_readme_17_1.png)
@@ -416,6 +422,9 @@ plot = prettymaps.plot('Centro Histórico, Porto Alegre', show = False)
plot.geodataframes['building']
```
Fetching geodataframes took 1.59 seconds
@@ -741,7 +750,7 @@ plot.geodataframes['building'][
].geometry[0]
```
/home/marcelo/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]`
/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)
@@ -749,7 +758,7 @@ plot.geodataframes['building'][
![svg](README_files/README_21_1.svg)
![svg](pictures/README/temp_readme_files/temp_readme_21_1.svg)
@@ -788,9 +797,12 @@ for ax,building in zip(np.concatenate(axes),buildings):
ax.autoscale(); ax.axis('off'); ax.axis('equal')
```
Fetching geodataframes took 2.01 seconds
![png](README_files/README_23_0.png)
![png](pictures/README/temp_readme_files/temp_readme_23_1.png)
@@ -816,6 +828,9 @@ _ = plot.ax.set_title(
)
```
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)
@@ -832,9 +847,12 @@ plot = prettymaps.plot(
)
```
Fetching geodataframes took 3.89 seconds
![png](README_files/README_27_0.png)
![png](pictures/README/temp_readme_files/temp_readme_27_1.png)
@@ -853,9 +871,12 @@ plot = prettymaps.plot(
)
```
Fetching geodataframes took 16.55 seconds
![png](README_files/README_29_0.png)
![png](pictures/README/temp_readme_files/temp_readme_29_1.png)
@@ -951,9 +972,14 @@ plot = prettymaps.multiplot(
)
```
Fetching geodataframes took 0.97 seconds
Fetching geodataframes took 1.15 seconds
Fetching geodataframes took 0.79 seconds
![png](README_files/README_33_0.png)
![png](pictures/README/temp_readme_files/temp_readme_33_1.png)
@@ -976,21 +1002,15 @@ plot = prettymaps.plot(
)
```
The autoreload extension is already loaded. To reload it, use:
%reload_ext autoreload
make: Entering directory '/home/marcelo/.cache/elevation/SRTM1'
make: Nothing to be done for 'download'.
make: Leaving directory '/home/marcelo/.cache/elevation/SRTM1'
make: Entering directory '/home/marcelo/.cache/elevation/SRTM1'
make: Nothing to be done for 'all'.
make: Leaving directory '/home/marcelo/.cache/elevation/SRTM1'
make: Entering directory '/home/marcelo/.cache/elevation/SRTM1'
cp SRTM1.vrt SRTM1.2d5b6f11e0e74b44a9386ba897fb0852.vrt
make: Leaving directory '/home/marcelo/.cache/elevation/SRTM1'
make: Entering directory '/home/marcelo/.cache/elevation/SRTM1'
gdal_translate -q -co TILED=YES -co COMPRESS=DEFLATE -co ZLEVEL=9 -co PREDICTOR=2 -projwin -157.90125854957773 21.364471426268267 -157.81006761682832 21.244615177105388 SRTM1.2d5b6f11e0e74b44a9386ba897fb0852.vrt /home/marcelo/Projects/Art/prettymaps/notebooks/elevationa.tif
rm -f SRTM1.2d5b6f11e0e74b44a9386ba897fb0852.vrt
make: Leaving directory '/home/marcelo/.cache/elevation/SRTM1'
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.
@@ -998,7 +1018,7 @@ plot = prettymaps.plot(
![png](README_files/README_35_2.png)
![png](pictures/README/temp_readme_files/temp_readme_35_2.png)
@@ -1023,8 +1043,11 @@ plot = prettymaps.plot(
)
```
Fetching geodataframes took 21.47 seconds
![png](README_files/README_37_0.png)
![png](pictures/README/temp_readme_files/temp_readme_37_1.png)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 MiB

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="100.0" height="100.0" viewBox="-51.23032682800001 -30.034281028 0.0006073560000103839 0.0008053559999900983" preserveAspectRatio="xMinYMin meet"><g transform="matrix(1,0,0,-1,0,-60.067756700000004)"><path fill-rule="evenodd" fill="#66cc99" stroke="#555555" stroke-width="1.6107119999801965e-05" opacity="0.6" d="M -51.2298412,-30.03353870000001 L -51.2298154,-30.033540599999984 L -51.2298126,-30.0335408 L -51.2298129,-30.033544300000003 L -51.2297808,-30.033546699999988 L -51.2297805,-30.033544400000007 L -51.2297758,-30.0335447 L -51.2297562,-30.0335462 L -51.2297493,-30.033546700000002 L -51.2297572,-30.033626999999992 L -51.22975770000001,-30.033632 L -51.2298302,-30.03362630000001 L -51.2298538,-30.033865000000006 L -51.2298442,-30.0338658 L -51.2298477,-30.033909500000004 L -51.2298412,-30.03390990000001 L -51.2298357,-30.03390249999999 L -51.2298133,-30.033912799999996 L -51.2298168,-30.0339188 L -51.2297959,-30.0339406 L -51.2297906,-30.033938999999993 L -51.2297826,-30.0339586 L -51.2297866,-30.033959199999998 L -51.2297957,-30.034019000000004 L -51.2297902,-30.034021400000004 L -51.229800700000006,-30.0340417 L -51.2298073,-30.0340393 L -51.2298344,-30.034053999999994 L -51.2298319,-30.034065200000004 L -51.2298509,-30.0340704 L -51.2298558,-30.034059600000003 L -51.2298704,-30.0340585 L -51.2298741,-30.034097 L -51.2298921,-30.034095599999993 L -51.2298985,-30.03416019999999 L -51.2298609,-30.034163 L -51.2298683,-30.03423859999999 L -51.2299531,-30.0342322 L -51.2299549,-30.0342512 L -51.2301846,-30.034234 L -51.2301825,-30.0342134 L -51.2302643,-30.03420729999999 L -51.2302569,-30.034132200000002 L -51.2302241,-30.034134599999994 L -51.2302178,-30.0340702 L -51.2302266,-30.0340694 L -51.2302229,-30.034030399999995 L -51.2302285,-30.03403000000001 L -51.230236500000004,-30.0340294 L -51.2302396,-30.034038299999995 L -51.230256600000004,-30.034034099999996 L -51.2302532,-30.0340242 L -51.230280900000004,-30.034001600000003 L -51.2302902,-30.0340033 L -51.23029700000001,-30.033981200000003 L -51.2302871,-30.0339784 L -51.2302841,-30.033920999999996 L -51.2302898,-30.033919600000008 L -51.2302806,-30.0338995 L -51.2302726,-30.033902400000002 L -51.2302486,-30.0338856 L -51.2302511,-30.033877399999994 L -51.2302264,-30.033868700000003 L -51.2302198,-30.033880699999997 L -51.2302148,-30.033878999999995 L -51.230213000000006,-30.033838099999997 L -51.2302054,-30.033838700000004 L -51.2301818,-30.0336 L -51.2302499,-30.033594899999997 L -51.2302471,-30.033566499999996 L -51.2302452,-30.033566699999998 L -51.2302423,-30.0335373 L -51.2302448,-30.033537100000004 L -51.2302419,-30.0335085 L -51.2302051,-30.0335112 L -51.2302053,-30.033513599999992 L -51.2301756,-30.0335159 L -51.2301752,-30.033512399999992 L -51.23014450000001,-30.033514699999994 L -51.2301449,-30.033518200000003 L -51.2300854,-30.033522700000002 L -51.2300843,-30.033511399999984 L -51.2300823,-30.0335115 L -51.2300635,-30.033512799999997 L -51.2300628,-30.033505500000008 L -51.2300334,-30.0335078 L -51.2300336,-30.03350989999999 L -51.2300339,-30.033513 L -51.22995280000001,-30.033519099999996 L -51.229952600000004,-30.033516999999996 L -51.2299524,-30.0335151 L -51.2299225,-30.03351729999999 L -51.2299228,-30.033520499999998 L -51.2298995,-30.03352229999999 L -51.2299009,-30.033537 L -51.2298443,-30.033541300000003 L -51.229844,-30.0335385 L -51.2298412,-30.03353870000001 z" /></g></svg>

Before

Width:  |  Height:  |  Size: 3.4 KiB

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="100.0" height="100.0" viewBox="-51.23032682800001 -30.034281028 0.0006073560000103839 0.0008053559999900983" preserveAspectRatio="xMinYMin meet"><g transform="matrix(1,0,0,-1,0,-60.067756700000004)"><path fill-rule="evenodd" fill="#66cc99" stroke="#555555" stroke-width="1.6107119999801965e-05" opacity="0.6" d="M -51.2298412,-30.03353870000001 L -51.2298154,-30.033540599999984 L -51.2298126,-30.0335408 L -51.2298129,-30.033544300000003 L -51.2297808,-30.033546700000002 L -51.2297805,-30.033544400000007 L -51.2297758,-30.0335447 L -51.2297562,-30.0335462 L -51.2297493,-30.033546699999995 L -51.2297572,-30.033627 L -51.22975770000001,-30.033632 L -51.2298302,-30.033626299999995 L -51.2298538,-30.033865 L -51.2298442,-30.033865799999994 L -51.2298477,-30.033909500000004 L -51.2298412,-30.03390990000001 L -51.2298357,-30.03390249999999 L -51.2298133,-30.033912799999996 L -51.2298168,-30.0339188 L -51.2297959,-30.0339406 L -51.2297906,-30.033939 L -51.2297826,-30.03395859999999 L -51.2297866,-30.033959199999998 L -51.2297957,-30.034019000000004 L -51.2297902,-30.034021399999997 L -51.229800700000006,-30.0340417 L -51.2298073,-30.0340393 L -51.2298344,-30.034053999999994 L -51.2298319,-30.034065200000008 L -51.2298509,-30.034070399999994 L -51.2298558,-30.034059600000003 L -51.2298704,-30.0340585 L -51.2298741,-30.034097 L -51.2298921,-30.034095599999993 L -51.2298985,-30.03416019999999 L -51.2298609,-30.034163 L -51.2298683,-30.034238599999984 L -51.2299531,-30.0342322 L -51.2299549,-30.0342512 L -51.2301846,-30.034234 L -51.2301825,-30.03421339999999 L -51.2302643,-30.03420729999999 L -51.2302569,-30.034132200000002 L -51.2302241,-30.034134599999994 L -51.2302178,-30.0340702 L -51.2302266,-30.034069400000007 L -51.2302229,-30.034030399999995 L -51.2302285,-30.03403000000001 L -51.230236500000004,-30.0340294 L -51.2302396,-30.034038299999995 L -51.230256600000004,-30.03403409999999 L -51.2302532,-30.0340242 L -51.230280900000004,-30.034001599999996 L -51.2302902,-30.0340033 L -51.23029700000001,-30.03398119999999 L -51.2302871,-30.0339784 L -51.2302841,-30.033920999999992 L -51.2302898,-30.033919600000008 L -51.2302806,-30.0338995 L -51.2302726,-30.033902399999995 L -51.2302486,-30.033885599999994 L -51.2302511,-30.033877399999998 L -51.2302264,-30.033868700000003 L -51.2302198,-30.033880699999987 L -51.2302148,-30.033878999999995 L -51.230213000000006,-30.03383810000001 L -51.2302054,-30.033838700000004 L -51.2301818,-30.0336 L -51.2302499,-30.033594899999997 L -51.2302471,-30.033566499999996 L -51.2302452,-30.033566699999998 L -51.2302423,-30.0335373 L -51.2302448,-30.033537100000004 L -51.2302419,-30.0335085 L -51.2302051,-30.033511199999992 L -51.2302053,-30.03351359999999 L -51.2301756,-30.0335159 L -51.2301752,-30.033512399999992 L -51.23014450000001,-30.0335147 L -51.2301449,-30.033518200000003 L -51.2300854,-30.033522699999995 L -51.2300843,-30.03351139999999 L -51.2300823,-30.0335115 L -51.2300635,-30.033512799999997 L -51.2300628,-30.033505500000008 L -51.2300334,-30.0335078 L -51.2300336,-30.03350989999999 L -51.2300339,-30.033513 L -51.22995280000001,-30.033519100000003 L -51.229952600000004,-30.033516999999996 L -51.2299524,-30.0335151 L -51.2299225,-30.03351729999999 L -51.2299228,-30.033520499999998 L -51.2298995,-30.03352229999999 L -51.2299009,-30.033536999999992 L -51.2298443,-30.033541300000003 L -51.229844,-30.0335385 L -51.2298412,-30.03353870000001 z" /></g></svg>

Before

Width:  |  Height:  |  Size: 3.4 KiB

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="100.0" height="100.0" viewBox="-51.23032682800001 -30.034281028 0.0006073560000103839 0.0008053559999900983" preserveAspectRatio="xMinYMin meet"><g transform="matrix(1,0,0,-1,0,-60.067756700000004)"><path fill-rule="evenodd" fill="#66cc99" stroke="#555555" stroke-width="1.6107119999801965e-05" opacity="0.6" d="M -51.2298412,-30.03353870000001 L -51.2298154,-30.033540599999984 L -51.2298126,-30.0335408 L -51.2298129,-30.033544300000003 L -51.2297808,-30.033546700000002 L -51.2297805,-30.033544400000007 L -51.2297758,-30.0335447 L -51.2297562,-30.0335462 L -51.2297493,-30.033546699999995 L -51.2297572,-30.033627 L -51.22975770000001,-30.033632 L -51.2298302,-30.033626299999995 L -51.2298538,-30.033865 L -51.2298442,-30.033865799999994 L -51.2298477,-30.033909500000004 L -51.2298412,-30.03390990000001 L -51.2298357,-30.03390249999999 L -51.2298133,-30.033912799999996 L -51.2298168,-30.0339188 L -51.2297959,-30.0339406 L -51.2297906,-30.033939 L -51.2297826,-30.03395859999999 L -51.2297866,-30.033959199999998 L -51.2297957,-30.034019000000004 L -51.2297902,-30.034021399999997 L -51.229800700000006,-30.0340417 L -51.2298073,-30.0340393 L -51.2298344,-30.034053999999994 L -51.2298319,-30.034065200000008 L -51.2298509,-30.034070399999994 L -51.2298558,-30.034059600000003 L -51.2298704,-30.0340585 L -51.2298741,-30.034097 L -51.2298921,-30.034095599999993 L -51.2298985,-30.03416019999999 L -51.2298609,-30.034163 L -51.2298683,-30.034238599999984 L -51.2299531,-30.0342322 L -51.2299549,-30.0342512 L -51.2301846,-30.034234 L -51.2301825,-30.03421339999999 L -51.2302643,-30.03420729999999 L -51.2302569,-30.034132200000002 L -51.2302241,-30.034134599999994 L -51.2302178,-30.0340702 L -51.2302266,-30.034069400000007 L -51.2302229,-30.034030399999995 L -51.2302285,-30.03403000000001 L -51.230236500000004,-30.0340294 L -51.2302396,-30.034038299999995 L -51.230256600000004,-30.03403409999999 L -51.2302532,-30.0340242 L -51.230280900000004,-30.034001599999996 L -51.2302902,-30.0340033 L -51.23029700000001,-30.03398119999999 L -51.2302871,-30.0339784 L -51.2302841,-30.033920999999992 L -51.2302898,-30.033919600000008 L -51.2302806,-30.0338995 L -51.2302726,-30.033902399999995 L -51.2302486,-30.033885599999994 L -51.2302511,-30.033877399999998 L -51.2302264,-30.033868700000003 L -51.2302198,-30.033880699999987 L -51.2302148,-30.033878999999995 L -51.230213000000006,-30.03383810000001 L -51.2302054,-30.033838700000004 L -51.2301818,-30.0336 L -51.2302499,-30.033594899999997 L -51.2302471,-30.033566499999996 L -51.2302452,-30.033566699999998 L -51.2302423,-30.0335373 L -51.2302448,-30.033537100000004 L -51.2302419,-30.0335085 L -51.2302051,-30.033511199999992 L -51.2302053,-30.03351359999999 L -51.2301756,-30.0335159 L -51.2301752,-30.033512399999992 L -51.23014450000001,-30.0335147 L -51.2301449,-30.033518200000003 L -51.2300854,-30.033522699999995 L -51.2300843,-30.03351139999999 L -51.2300823,-30.0335115 L -51.2300635,-30.033512799999997 L -51.2300628,-30.033505500000008 L -51.2300334,-30.0335078 L -51.2300336,-30.03350989999999 L -51.2300339,-30.033513 L -51.22995280000001,-30.033519100000003 L -51.229952600000004,-30.033516999999996 L -51.2299524,-30.0335151 L -51.2299225,-30.03351729999999 L -51.2299228,-30.033520499999998 L -51.2298995,-30.03352229999999 L -51.2299009,-30.033536999999992 L -51.2298443,-30.033541300000003 L -51.229844,-30.0335385 L -51.2298412,-30.03353870000001 z" /></g></svg>

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="100.0" height="100.0" viewBox="-51.23032682800001 -30.034281028 0.0006073560000103839 0.0008053559999900983" preserveAspectRatio="xMinYMin meet"><g transform="matrix(1,0,0,-1,0,-60.067756700000004)"><path fill-rule="evenodd" fill="#66cc99" stroke="#555555" stroke-width="1.6107119999801965e-05" opacity="0.6" d="M -51.2298412,-30.03353870000001 L -51.2298154,-30.033540599999984 L -51.2298126,-30.0335408 L -51.2298129,-30.033544300000003 L -51.2297808,-30.033546700000002 L -51.2297805,-30.033544400000007 L -51.2297758,-30.0335447 L -51.2297562,-30.0335462 L -51.2297493,-30.033546699999995 L -51.2297572,-30.033627 L -51.22975770000001,-30.033632 L -51.2298302,-30.033626299999995 L -51.2298538,-30.033865 L -51.2298442,-30.033865799999994 L -51.2298477,-30.033909500000004 L -51.2298412,-30.03390990000001 L -51.2298357,-30.03390249999999 L -51.2298133,-30.033912799999996 L -51.2298168,-30.0339188 L -51.2297959,-30.0339406 L -51.2297906,-30.033939 L -51.2297826,-30.03395859999999 L -51.2297866,-30.033959199999998 L -51.2297957,-30.034019000000004 L -51.2297902,-30.034021399999997 L -51.229800700000006,-30.0340417 L -51.2298073,-30.0340393 L -51.2298344,-30.034053999999994 L -51.2298319,-30.034065200000008 L -51.2298509,-30.034070399999994 L -51.2298558,-30.034059600000003 L -51.2298704,-30.0340585 L -51.2298741,-30.034097 L -51.2298921,-30.034095599999993 L -51.2298985,-30.03416019999999 L -51.2298609,-30.034163 L -51.2298683,-30.034238599999984 L -51.2299531,-30.0342322 L -51.2299549,-30.0342512 L -51.2301846,-30.034234 L -51.2301825,-30.03421339999999 L -51.2302643,-30.03420729999999 L -51.2302569,-30.034132200000002 L -51.2302241,-30.034134599999994 L -51.2302178,-30.0340702 L -51.2302266,-30.034069400000007 L -51.2302229,-30.034030399999995 L -51.2302285,-30.03403000000001 L -51.230236500000004,-30.0340294 L -51.2302396,-30.034038299999995 L -51.230256600000004,-30.03403409999999 L -51.2302532,-30.0340242 L -51.230280900000004,-30.034001599999996 L -51.2302902,-30.0340033 L -51.23029700000001,-30.03398119999999 L -51.2302871,-30.0339784 L -51.2302841,-30.033920999999992 L -51.2302898,-30.033919600000008 L -51.2302806,-30.0338995 L -51.2302726,-30.033902399999995 L -51.2302486,-30.033885599999994 L -51.2302511,-30.033877399999998 L -51.2302264,-30.033868700000003 L -51.2302198,-30.033880699999987 L -51.2302148,-30.033878999999995 L -51.230213000000006,-30.03383810000001 L -51.2302054,-30.033838700000004 L -51.2301818,-30.0336 L -51.2302499,-30.033594899999997 L -51.2302471,-30.033566499999996 L -51.2302452,-30.033566699999998 L -51.2302423,-30.0335373 L -51.2302448,-30.033537100000004 L -51.2302419,-30.0335085 L -51.2302051,-30.033511199999992 L -51.2302053,-30.03351359999999 L -51.2301756,-30.0335159 L -51.2301752,-30.033512399999992 L -51.23014450000001,-30.0335147 L -51.2301449,-30.033518200000003 L -51.2300854,-30.033522699999995 L -51.2300843,-30.03351139999999 L -51.2300823,-30.0335115 L -51.2300635,-30.033512799999997 L -51.2300628,-30.033505500000008 L -51.2300334,-30.0335078 L -51.2300336,-30.03350989999999 L -51.2300339,-30.033513 L -51.22995280000001,-30.033519100000003 L -51.229952600000004,-30.033516999999996 L -51.2299524,-30.0335151 L -51.2299225,-30.03351729999999 L -51.2299228,-30.033520499999998 L -51.2298995,-30.03352229999999 L -51.2299009,-30.033536999999992 L -51.2298443,-30.033541300000003 L -51.229844,-30.0335385 L -51.2298412,-30.03353870000001 z" /></g></svg>

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 780 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 779 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

0
app.py Normal file → Executable file
View File

21
docs/api.md Normal file
View File

@@ -0,0 +1,21 @@
# API Reference
This page documents the main modules and functions of the `prettymaps` library. The API documentation is auto-generated from the code and includes all public classes and functions.
## Modules
- **draw**: Core drawing and plotting functions, including map rendering, layer management, and style handling.
- **fetch**: Functions for fetching and processing OpenStreetMap data and elevation.
- **utils**: Utility functions for logging, timing, and other helpers.
---
::: prettymaps.draw
---
::: prettymaps.fetch
---
::: prettymaps.utils

27
docs/index.md Normal file
View File

@@ -0,0 +1,27 @@
# prettymaps
A minimal Python library to draw pretty maps from OpenStreetMap data.
- [Usage](usage.md)
- [API Reference](api.md)
## Features
- Draw beautiful maps from OSM data
- Highly customizable layers and styles
- Preset management
- Keypoint highlighting
## Installation
```bash
pip install prettymaps
```
## Quick Example
```python
import prettymaps
prettymaps.plot("Porto Alegre")
```
---
Data © OpenStreetMap contributors

4
docs/requirements.txt Normal file
View File

@@ -0,0 +1,4 @@
mkdocs
mkdocs-material
mkdocstrings
mkdocstrings-python

64
docs/usage.md Normal file
View File

@@ -0,0 +1,64 @@
# Usage
## Basic Usage
The main entry point is the `plot()` function:
```python
import prettymaps
prettymaps.plot("Porto Alegre")
```
This will generate a map for the given location using default layers and styles.
## Customizing Layers and Styles
You can customize which OpenStreetMap layers are shown and how they are styled:
```python
layers = {
"perimeter": {},
"streets": {"width": 8},
"buildings": {},
}
style = {
"perimeter": {"fc": "#f2efe9"},
"streets": {"fc": "#cccccc", "ec": "#333333"},
"buildings": {"fc": "#bdbdbd"},
}
prettymaps.plot("Porto Alegre", layers=layers, style=style)
```
## Using Presets
Presets are reusable configurations for layers and styles. You can load, save, or update presets:
```python
prettymaps.plot("Porto Alegre", preset="default")
```
You can also create your own presets and save them for later use.
## Highlighting Keypoints
You can highlight specific keypoints (e.g., landmarks) on the map:
```python
keypoints = {
"tags": {"tourism": "attraction"},
"kwargs": {"bbox": {"fc": "yellow"}},
}
prettymaps.plot("Porto Alegre", keypoints=keypoints)
```
## Saving Maps
You can save the generated map to a file:
```python
prettymaps.plot("Porto Alegre", save_as="map.png")
```
---
See the [API Reference](api.md) for details on all functions and parameters.

21
mkdocs.yml Normal file
View File

@@ -0,0 +1,21 @@
site_name: prettymaps
site_url: https://yourusername.github.io/prettymaps/
docs_dir: docs
nav:
- Home: index.md
- Usage: usage.md
- API Reference: api.md
theme:
name: material
palette:
primary: indigo
accent: pink
plugins:
- search
- mkdocstrings:
handlers:
python:
options:
show_root_heading: true
show_source: true
docstring_style: google

168
notebooks/examples.ipynb Normal file → Executable file

File diff suppressed because one or more lines are too long

0
packages.txt Normal file → Executable file
View File

View File

Before

Width:  |  Height:  |  Size: 168 KiB

After

Width:  |  Height:  |  Size: 168 KiB

View File

Before

Width:  |  Height:  |  Size: 100 KiB

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="100.0" height="100.0" viewBox="-51.23032682800001 -30.034281028 0.0006073560000103839 0.0008053559999900983" preserveAspectRatio="xMinYMin meet"><g transform="matrix(1,0,0,-1,0,-60.067756700000004)"><path fill-rule="evenodd" fill="#66cc99" stroke="#555555" stroke-width="1.6107119999801965e-05" opacity="0.6" d="M -51.2298412,-30.03353870000001 L -51.2298154,-30.033540599999984 L -51.2298126,-30.0335408 L -51.2298129,-30.033544300000003 L -51.2297808,-30.033546700000002 L -51.2297805,-30.033544400000007 L -51.2297758,-30.033544699999993 L -51.2297562,-30.033546199999993 L -51.2297493,-30.033546699999995 L -51.2297572,-30.033627000000006 L -51.22975770000001,-30.033632000000008 L -51.2298302,-30.033626299999995 L -51.2298538,-30.033865 L -51.2298442,-30.033865799999994 L -51.2298477,-30.033909500000004 L -51.2298412,-30.03390990000001 L -51.2298357,-30.03390249999999 L -51.2298133,-30.033912799999996 L -51.2298168,-30.0339188 L -51.2297959,-30.0339406 L -51.2297906,-30.033939 L -51.2297826,-30.0339586 L -51.2297866,-30.033959199999998 L -51.2297957,-30.034019000000004 L -51.2297902,-30.03402139999999 L -51.229800700000006,-30.0340417 L -51.2298073,-30.034039299999986 L -51.2298344,-30.034053999999994 L -51.2298319,-30.034065200000008 L -51.2298509,-30.034070399999994 L -51.2298558,-30.034059600000003 L -51.2298704,-30.0340585 L -51.2298741,-30.034097 L -51.2298921,-30.034095599999993 L -51.2298985,-30.034160200000002 L -51.2298609,-30.034163 L -51.2298683,-30.034238599999984 L -51.2299531,-30.0342322 L -51.2299549,-30.0342512 L -51.2301846,-30.034234 L -51.2301825,-30.03421339999999 L -51.2302643,-30.03420729999999 L -51.2302569,-30.034132200000002 L -51.2302241,-30.0341346 L -51.2302178,-30.0340702 L -51.2302266,-30.034069400000007 L -51.2302229,-30.034030399999995 L -51.2302285,-30.03403 L -51.230236500000004,-30.0340294 L -51.2302396,-30.034038299999995 L -51.230256600000004,-30.03403409999999 L -51.2302532,-30.0340242 L -51.230280900000004,-30.034001599999996 L -51.2302902,-30.0340033 L -51.23029700000001,-30.033981199999996 L -51.2302871,-30.0339784 L -51.2302841,-30.033920999999992 L -51.2302898,-30.033919600000008 L -51.2302806,-30.0338995 L -51.2302726,-30.033902399999995 L -51.2302486,-30.033885599999994 L -51.2302511,-30.033877399999998 L -51.2302264,-30.033868700000003 L -51.2302198,-30.03388069999999 L -51.2302148,-30.033878999999995 L -51.230213000000006,-30.03383809999999 L -51.2302054,-30.033838700000004 L -51.2301818,-30.0336 L -51.2302499,-30.033594899999997 L -51.2302471,-30.033566499999996 L -51.2302452,-30.033566699999998 L -51.2302423,-30.0335373 L -51.2302448,-30.033537100000004 L -51.2302419,-30.0335085 L -51.2302051,-30.033511199999992 L -51.2302053,-30.03351359999999 L -51.2301756,-30.0335159 L -51.2301752,-30.033512399999992 L -51.23014450000001,-30.033514699999994 L -51.2301449,-30.03351820000001 L -51.2300854,-30.033522700000002 L -51.2300843,-30.03351139999999 L -51.2300823,-30.033511500000007 L -51.2300635,-30.033512799999997 L -51.2300628,-30.033505500000008 L -51.2300334,-30.0335078 L -51.2300336,-30.03350989999999 L -51.2300339,-30.033513 L -51.22995280000001,-30.033519100000003 L -51.229952600000004,-30.033516999999996 L -51.2299524,-30.0335151 L -51.2299225,-30.03351729999999 L -51.2299228,-30.033520499999998 L -51.2298995,-30.03352229999999 L -51.2299009,-30.033536999999992 L -51.2298443,-30.033541300000003 L -51.229844,-30.0335385 L -51.2298412,-30.03353870000001 z" /></g></svg>

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 691 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 958 KiB

0
prettymaps/__init__.py Normal file → Executable file
View File

21
prettymaps/draw.py Normal file → Executable file
View File

@@ -16,16 +16,19 @@ 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/>.
"""
import cv2
# Standard library imports
import json
import logging
import os
import time
import pathlib
import warnings
from copy import deepcopy
from dataclasses import dataclass
from typing import Any, Callable, Dict, Iterable, List, Optional, Tuple, Union
# Third-party imports
import cv2
import geopandas as gp
import matplotlib
import numpy as np
@@ -47,8 +50,11 @@ from shapely.geometry import (
box,
)
from shapely.geometry.base import BaseGeometry
from sklearn.preprocessing import MinMaxScaler
from thefuzz import fuzz
import shutil
# Local imports
from .fetch import get_gdfs, obtain_elevation, get_keypoints
from .utils import log_execution_time
@@ -539,7 +545,6 @@ def draw_hillshade(
ls = LightSource(azdeg=azdeg, altdeg=altdeg)
hillshade = ls.hillshade(elevation_data, vert_exag=vert_exag, dx=dx, dy=dy)
# Convert hillshade to RGBA
from sklearn.preprocessing import MinMaxScaler
# hillshade = np.clip(hillshade, 0, np.inf)
# hillshade = MinMaxScaler((0, 1)).fit_transform(hillshade)
@@ -562,6 +567,15 @@ def draw_hillshade(
ax.set_xlim(min_x, max_x)
ax.set_ylim(min_y, max_y)
# Delete the ./SRTM1 folder after drawing hillshade
srtm1_dir = os.path.join(os.getcwd(), "SRTM1")
if os.path.isdir(srtm1_dir):
try:
shutil.rmtree(srtm1_dir)
except Exception as e:
if logging:
print(f"Warning: Failed to delete {srtm1_dir}: {e}")
@log_execution_time
def create_background(
@@ -1178,7 +1192,10 @@ def plot(
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)

0
prettymaps/fetch.py Normal file → Executable file
View File

0
prettymaps/presets/abraca-redencao.json Normal file → Executable file
View File

0
prettymaps/presets/barcelona-plotter.json Normal file → Executable file
View File

0
prettymaps/presets/barcelona.json Normal file → Executable file
View File

0
prettymaps/presets/cb-bf-f.json Normal file → Executable file
View File

0
prettymaps/presets/default.json Normal file → Executable file
View File

0
prettymaps/presets/heerhugowaard.json Normal file → Executable file
View File

0
prettymaps/presets/macao.json Normal file → Executable file
View File

0
prettymaps/presets/minimal.json Normal file → Executable file
View File

0
prettymaps/presets/plotter.json Normal file → Executable file
View File

0
prettymaps/presets/tijuca.json Normal file → Executable file
View File

0
prettymaps/utils.py Normal file → Executable file
View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 985 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 773 KiB

0
requirements.txt Normal file → Executable file
View File

View File

@@ -1,9 +0,0 @@
#!/bin/sh
# Resolve all dependencies that the application requires to run.
# Stop on errors
set -e
cd "$(dirname "$0")/.."
pip3 install -e .

View File

@@ -0,0 +1,35 @@
#!/bin/bash
# Exit on error
set -e
NOTEBOOKS_DIR="notebooks"
README_FILE="README.md"
EXAMPLES_NOTEBOOK="$NOTEBOOKS_DIR/examples.ipynb"
PICTURES_DIR="pictures/README"
# Check if examples.ipynb exists
if [ ! -f "$EXAMPLES_NOTEBOOK" ]; then
echo "examples.ipynb not found in $NOTEBOOKS_DIR. Exiting."
exit 1
fi
# Create pictures/README directory if it doesn't exist
mkdir -p "$PICTURES_DIR"
# Export examples.ipynb to markdown, putting images in pictures/README
jupyter nbconvert --to markdown "$EXAMPLES_NOTEBOOK" --output temp_readme --output-dir "$PICTURES_DIR"
# Move the markdown file to the root as README.md
mv "$PICTURES_DIR/temp_readme.md" "$README_FILE"
# Move all images to pictures/README (they are already there, but this ensures they stay organized)
# (No-op if already in place)
# Update image links in README.md to point to pictures/README/
sed -i '' 's|(temp_readme_files/|(pictures/README/temp_readme_files/|g' "$README_FILE"
# Optionally, clean up any old temp_readme_files in root
# rm -rf temp_readme_files
echo "README.md has been replaced with the markdown export of $EXAMPLES_NOTEBOOK, and images are in $PICTURES_DIR."

0
setup.py Normal file → Executable file
View File

53
setup.sh Normal file → Executable file
View File

@@ -1,18 +1,47 @@
#! /bin/bash
#!/bin/bash
# Change permissions for STRM folder
chmod -R 755 ./SRTM1
set -e
# Update package list
apt-get update
# Detect OS
OS="$(uname)"
echo "Detected OS: $OS"
# Install essential build tools including make and gcc
apt-get install -y build-essential make gcc
# Change permissions for SRTM1 folder (Linux/macOS only)
if [ -d "./SRTM1" ]; then
chmod -R 755 ./SRTM1
fi
# Optionally install other dependencies (e.g., for elevation or other libraries)
apt-get install -y python3-dev libgdal-dev
if [[ "$OS" == "Linux" ]]; then
# Linux (Debian/Ubuntu)
echo "Updating package list and installing dependencies for Linux..."
sudo apt-get update
sudo apt-get install -y build-essential make gcc python3-dev libgdal-dev gdal-bin
sudo apt-get clean
# Clean up to reduce the image size
apt-get clean
elif [[ "$OS" == "Darwin" ]]; then
# macOS
echo "Installing dependencies for macOS (requires Homebrew)..."
if ! command -v brew &>/dev/null; then
echo "Homebrew not found. Please install Homebrew first: https://brew.sh/"
exit 1
fi
brew update
brew install gdal make gcc
pip install git+https://github.com/marceloprates/prettymaps.git
elif [[ "$OS" =~ "MINGW" || "$OS" =~ "MSYS" || "$OS" =~ "CYGWIN" ]]; then
# Windows (Git Bash, MSYS, Cygwin)
echo "Windows detected. Please install the following manually:"
echo "- GDAL (https://gdal.org/download.html or via Conda: conda install -c conda-forge gdal)"
echo "- Make and GCC (optional, for advanced features)"
echo "- Python 3 and pip"
echo "Then run: pip install -e ."
exit 0
else
echo "Unsupported OS: $OS"
exit 1
fi
# Install Python dependencies
pip install -e .
echo "Setup complete!"

300
tests/test.py Normal file
View File

@@ -0,0 +1,300 @@
import pytest
import prettymaps
import osmnx as ox
import geopandas as gpd
# Helper to get a GeoDataFrame for a given query
def get_gdf(query = "Bom Fim, Porto Alegre, Brasil"):
return ox.geocode_to_gdf(query)
# Test that presets() returns a DataFrame with known presets
def test_presets():
df = prettymaps.presets()
assert not df.empty
assert 'preset' in df.columns
assert 'params' in df.columns
assert 'default' in df['preset'].values
# Test that preset() returns a Preset object with params
def test_preset():
p = prettymaps.preset('default')
assert hasattr(p, 'params')
assert isinstance(p.params, dict)
assert 'layers' in p.params
assert 'style' in p.params
# Test create_preset and reading it back
def test_create_and_read_preset(tmp_path):
name = 'pytest-temp-preset'
layers = {'building': {'tags': {'building': True}}}
style = {'building': {'fc': '#fff'}}
prettymaps.create_preset(name, layers=layers, style=style)
p = prettymaps.preset(name)
assert p.params['layers'] == layers
assert p.params['style'] == style
# Test Subplot class
def test_subplot():
s = prettymaps.Subplot('Porto Alegre', style={'building': {'fc': '#fff'}})
assert s.query == 'Porto Alegre'
assert 'style' in s.kwargs
# Test plot() basic call returns expected attributes (mocked heavy work)
def test_plot_smoke(monkeypatch):
import prettymaps.draw as draw
monkeypatch.setattr(draw, 'get_gdfs', lambda *a, **k: {'perimeter': get_gdf(), 'building': get_gdf()})
monkeypatch.setattr(draw, 'create_background', lambda *a, **k: (None, 0,0,0,0,0,0))
monkeypatch.setattr(draw, 'draw_layers', lambda *a, **k: None)
monkeypatch.setattr(draw, 'draw_keypoints', lambda *a, **k: None)
monkeypatch.setattr(draw, 'draw_background', lambda *a, **k: None)
monkeypatch.setattr(draw, 'draw_credit', lambda *a, **k: None)
monkeypatch.setattr(draw, 'draw_hillshade', lambda *a, **k: None)
result = prettymaps.plot('Porto Alegre', show=False)
assert hasattr(result, 'geodataframes')
assert hasattr(result, 'fig')
assert hasattr(result, 'ax')
# Test multiplot() with two subplots (mocked)
def test_multiplot_smoke(monkeypatch):
import prettymaps.draw as draw
monkeypatch.setattr(draw, 'plot', lambda *a, **k: type('FakePlot', (), {'geodataframes': {}, 'fig': None, 'ax': None, 'background': None, 'keypoints': None})())
s1 = prettymaps.Subplot('Cidade Baixa, Porto Alegre')
s2 = prettymaps.Subplot('Bom Fim, Porto Alegre')
prettymaps.multiplot(s1, s2, figsize=(4,4))
# Test plot() with a (lat, lon) tuple as query (mocked)
def test_plot_latlon_tuple(monkeypatch):
import prettymaps.draw as draw
monkeypatch.setattr(draw, 'get_gdfs', lambda *a, **k: {'perimeter': get_gdf(), 'building': get_gdf()})
monkeypatch.setattr(draw, 'create_background', lambda *a, **k: (None, 0,0,0,0,0,0))
monkeypatch.setattr(draw, 'draw_layers', lambda *a, **k: None)
monkeypatch.setattr(draw, 'draw_keypoints', lambda *a, **k: None)
monkeypatch.setattr(draw, 'draw_background', lambda *a, **k: None)
monkeypatch.setattr(draw, 'draw_credit', lambda *a, **k: None)
monkeypatch.setattr(draw, 'draw_hillshade', lambda *a, **k: None)
result = prettymaps.plot((41.39, 2.17), show=False)
assert hasattr(result, 'geodataframes')
assert hasattr(result, 'fig')
assert hasattr(result, 'ax')
# Test plot() with custom layers and style (mocked)
def test_plot_custom_layers_style(monkeypatch):
import prettymaps.draw as draw
monkeypatch.setattr(draw, 'get_gdfs', lambda *a, **k: {'perimeter': get_gdf(), 'building': get_gdf()})
monkeypatch.setattr(draw, 'create_background', lambda *a, **k: (None, 0,0,0,0,0,0))
monkeypatch.setattr(draw, 'draw_layers', lambda *a, **k: None)
monkeypatch.setattr(draw, 'draw_keypoints', lambda *a, **k: None)
monkeypatch.setattr(draw, 'draw_background', lambda *a, **k: None)
monkeypatch.setattr(draw, 'draw_credit', lambda *a, **k: None)
monkeypatch.setattr(draw, 'draw_hillshade', lambda *a, **k: None)
layers = {'building': {'tags': {'building': True}}}
style = {'building': {'fc': '#fff'}}
result = prettymaps.plot('Porto Alegre', layers=layers, style=style, show=False)
assert hasattr(result, 'geodataframes')
# Test plot() with radius set to False (mocked)
def test_plot_radius_false(monkeypatch):
import prettymaps.draw as draw
monkeypatch.setattr(draw, 'get_gdfs', lambda *a, **k: {'perimeter': get_gdf(), 'building': get_gdf()})
monkeypatch.setattr(draw, 'create_background', lambda *a, **k: (None, 0,0,0,0,0,0))
monkeypatch.setattr(draw, 'draw_layers', lambda *a, **k: None)
monkeypatch.setattr(draw, 'draw_keypoints', lambda *a, **k: None)
monkeypatch.setattr(draw, 'draw_background', lambda *a, **k: None)
monkeypatch.setattr(draw, 'draw_credit', lambda *a, **k: None)
monkeypatch.setattr(draw, 'draw_hillshade', lambda *a, **k: None)
result = prettymaps.plot('Porto Alegre', radius=False, show=False)
assert hasattr(result, 'geodataframes')
# Test plot() with keypoints argument (mocked)
def test_plot_keypoints(monkeypatch):
import prettymaps.draw as draw
monkeypatch.setattr(draw, 'get_gdfs', lambda *a, **k: {'perimeter': get_gdf(), 'building': get_gdf()})
monkeypatch.setattr(draw, 'create_background', lambda *a, **k: (None, 0,0,0,0,0,0))
monkeypatch.setattr(draw, 'draw_layers', lambda *a, **k: None)
monkeypatch.setattr(draw, 'draw_keypoints', lambda *a, **k: 'mocked_keypoints')
monkeypatch.setattr(draw, 'draw_background', lambda *a, **k: None)
monkeypatch.setattr(draw, 'draw_credit', lambda *a, **k: None)
monkeypatch.setattr(draw, 'draw_hillshade', lambda *a, **k: None)
keypoints = {'tags': {'natural': ['beach']}, 'specific': {'pedra branca': {'tags': {'natural': ['peak']}}}}
result = prettymaps.plot('Garopaba', keypoints=keypoints, show=False)
assert result.keypoints == 'mocked_keypoints'
# Test plot() with postprocessing function (mocked)
def test_plot_postprocessing(monkeypatch):
import prettymaps.draw as draw
monkeypatch.setattr(draw, 'get_gdfs', lambda *a, **k: {'perimeter': get_gdf(), 'building': get_gdf()})
monkeypatch.setattr(draw, 'create_background', lambda *a, **k: (None, 0,0,0,0,0,0))
monkeypatch.setattr(draw, 'draw_layers', lambda *a, **k: None)
monkeypatch.setattr(draw, 'draw_keypoints', lambda *a, **k: None)
monkeypatch.setattr(draw, 'draw_background', lambda *a, **k: None)
monkeypatch.setattr(draw, 'draw_credit', lambda *a, **k: None)
monkeypatch.setattr(draw, 'draw_hillshade', lambda *a, **k: None)
def postproc(gdfs):
gdfs['custom'] = 123
return gdfs
result = prettymaps.plot('Porto Alegre', postprocessing=postproc, show=False)
assert result.geodataframes['custom'] == 123
# Test plot() with save_as argument (mocked file save)
def test_plot_save_as(monkeypatch, tmp_path):
import prettymaps.draw as draw
monkeypatch.setattr(draw, 'get_gdfs', lambda *a, **k: {'perimeter': get_gdf(), 'building': get_gdf()})
monkeypatch.setattr(draw, 'create_background', lambda *a, **k: (None, 0,0,0,0,0,0))
monkeypatch.setattr(draw, 'draw_layers', lambda *a, **k: None)
monkeypatch.setattr(draw, 'draw_keypoints', lambda *a, **k: None)
monkeypatch.setattr(draw, 'draw_background', lambda *a, **k: None)
monkeypatch.setattr(draw, 'draw_credit', lambda *a, **k: None)
monkeypatch.setattr(draw, 'draw_hillshade', lambda *a, **k: None)
monkeypatch.setattr('matplotlib.pyplot.savefig', lambda *a, **k: None)
save_path = tmp_path / 'out.png'
result = prettymaps.plot('Porto Alegre', save_as=str(save_path), show=False)
assert hasattr(result, 'geodataframes')
# Test plot() with circle, radius, and dilate arguments (mocked)
def test_plot_circle_radius_dilate(monkeypatch):
import prettymaps.draw as draw
monkeypatch.setattr(draw, 'get_gdfs', lambda *a, **k: {'perimeter': get_gdf(), 'building': get_gdf()})
monkeypatch.setattr(draw, 'create_background', lambda *a, **k: (None, 0,0,0,0,0,0))
monkeypatch.setattr(draw, 'draw_layers', lambda *a, **k: None)
monkeypatch.setattr(draw, 'draw_keypoints', lambda *a, **k: None)
monkeypatch.setattr(draw, 'draw_background', lambda *a, **k: None)
monkeypatch.setattr(draw, 'draw_credit', lambda *a, **k: None)
monkeypatch.setattr(draw, 'draw_hillshade', lambda *a, **k: None)
result = prettymaps.plot('Porto Alegre', circle=True, radius=1000, dilate=50, show=False)
assert hasattr(result, 'geodataframes')
# Test plot() with mode='plotter' (mocked vsketch)
def test_plot_mode_plotter(monkeypatch):
import prettymaps.draw as draw
class DummyVsk:
def display(self):
pass
monkeypatch.setattr(draw, 'get_gdfs', lambda *a, **k: {'perimeter': get_gdf(), 'building': get_gdf()})
monkeypatch.setattr(draw, 'create_background', lambda *a, **k: (None, 0,0,0,0,0,0))
monkeypatch.setattr(draw, 'draw_layers', lambda *a, **k: None)
monkeypatch.setattr(draw, 'draw_keypoints', lambda *a, **k: None)
monkeypatch.setattr(draw, 'draw_background', lambda *a, **k: None)
monkeypatch.setattr(draw, 'draw_credit', lambda *a, **k: None)
monkeypatch.setattr(draw, 'draw_hillshade', lambda *a, **k: None)
monkeypatch.setattr(draw, 'init_plot', lambda *a, **k: (None, None, DummyVsk()))
result = prettymaps.plot('Porto Alegre', mode='plotter', show=True)
assert hasattr(result, 'geodataframes')
# Test multiplot() with custom Subplot styles (mocked)
def test_multiplot_custom_subplots(monkeypatch):
import prettymaps.draw as draw
monkeypatch.setattr(draw, 'plot', lambda *a, **k: type('FakePlot', (), {'geodataframes': {}, 'fig': None, 'ax': None, 'background': None, 'keypoints': None})())
s1 = prettymaps.Subplot('Cidade Baixa, Porto Alegre', style={'building': {'palette': ['#49392C']}})
s2 = prettymaps.Subplot('Bom Fim, Porto Alegre', style={'building': {'palette': ['#BA2D0B']}})
prettymaps.multiplot(s1, s2, figsize=(4,4))
# Test plot() returns object with all expected attributes (mocked)
def test_plot_types(monkeypatch):
import prettymaps.draw as draw
monkeypatch.setattr(draw, 'get_gdfs', lambda *a, **k: {'perimeter': get_gdf(), 'building': get_gdf()})
monkeypatch.setattr(draw, 'create_background', lambda *a, **k: (None, 0,0,0,0,0,0))
monkeypatch.setattr(draw, 'draw_layers', lambda *a, **k: None)
monkeypatch.setattr(draw, 'draw_keypoints', lambda *a, **k: None)
monkeypatch.setattr(draw, 'draw_background', lambda *a, **k: None)
monkeypatch.setattr(draw, 'draw_credit', lambda *a, **k: None)
monkeypatch.setattr(draw, 'draw_hillshade', lambda *a, **k: None)
result = prettymaps.plot('Porto Alegre', show=False)
assert hasattr(result, 'geodataframes')
assert hasattr(result, 'fig')
assert hasattr(result, 'ax')
assert hasattr(result, 'background')
assert hasattr(result, 'keypoints')
# Test plot() with credit argument (mocked)
def test_plot_credit(monkeypatch):
import prettymaps.draw as draw
monkeypatch.setattr(draw, 'get_gdfs', lambda *a, **k: {'perimeter': get_gdf(), 'building': get_gdf()})
monkeypatch.setattr(draw, 'create_background', lambda *a, **k: (None, 0,0,0,0,0,0))
monkeypatch.setattr(draw, 'draw_layers', lambda *a, **k: None)
monkeypatch.setattr(draw, 'draw_keypoints', lambda *a, **k: None)
monkeypatch.setattr(draw, 'draw_background', lambda *a, **k: None)
monkeypatch.setattr(draw, 'draw_credit', lambda *a, **k: None)
monkeypatch.setattr(draw, 'draw_hillshade', lambda *a, **k: None)
credit = {'text': 'Test credit'}
result = prettymaps.plot('Porto Alegre', credit=credit, show=False)
assert hasattr(result, 'geodataframes')
# Test plot() with custom figsize argument (mocked)
def test_plot_custom_figsize(monkeypatch):
import prettymaps.draw as draw
monkeypatch.setattr(draw, 'get_gdfs', lambda *a, **k: {'perimeter': get_gdf(), 'building': get_gdf()})
monkeypatch.setattr(draw, 'create_background', lambda *a, **k: (None, 0,0,0,0,0,0))
monkeypatch.setattr(draw, 'draw_layers', lambda *a, **k: None)
monkeypatch.setattr(draw, 'draw_keypoints', lambda *a, **k: None)
monkeypatch.setattr(draw, 'draw_background', lambda *a, **k: None)
monkeypatch.setattr(draw, 'draw_credit', lambda *a, **k: None)
monkeypatch.setattr(draw, 'draw_hillshade', lambda *a, **k: None)
result = prettymaps.plot('Porto Alegre', figsize=(5,5), show=False)
assert hasattr(result, 'geodataframes')
# Test plot() with x, y, scale_x, scale_y, rotation params (mocked)
def test_plot_transform_params(monkeypatch):
import prettymaps.draw as draw
monkeypatch.setattr(draw, 'get_gdfs', lambda *a, **k: {'perimeter': get_gdf(), 'building': get_gdf()})
monkeypatch.setattr(draw, 'create_background', lambda *a, **k: (None, 0,0,0,0,0,0))
monkeypatch.setattr(draw, 'draw_layers', lambda *a, **k: None)
monkeypatch.setattr(draw, 'draw_keypoints', lambda *a, **k: None)
monkeypatch.setattr(draw, 'draw_background', lambda *a, **k: None)
monkeypatch.setattr(draw, 'draw_credit', lambda *a, **k: None)
monkeypatch.setattr(draw, 'draw_hillshade', lambda *a, **k: None)
result = prettymaps.plot('Porto Alegre', x=1, y=2, scale_x=0.5, scale_y=0.5, rotation=45, show=False)
assert hasattr(result, 'geodataframes')
# Test plot() with show=True and show=False (mocked plt.show)
def test_plot_show_true_false(monkeypatch):
import prettymaps.draw as draw
monkeypatch.setattr(draw, 'get_gdfs', lambda *a, **k: {'perimeter': get_gdf(), 'building': get_gdf()})
monkeypatch.setattr(draw, 'create_background', lambda *a, **k: (None, 0,0,0,0,0,0))
monkeypatch.setattr(draw, 'draw_layers', lambda *a, **k: None)
monkeypatch.setattr(draw, 'draw_keypoints', lambda *a, **k: None)
monkeypatch.setattr(draw, 'draw_background', lambda *a, **k: None)
monkeypatch.setattr(draw, 'draw_credit', lambda *a, **k: None)
monkeypatch.setattr(draw, 'draw_hillshade', lambda *a, **k: None)
monkeypatch.setattr('matplotlib.pyplot.show', lambda *a, **k: None)
prettymaps.plot('Porto Alegre', show=True)
prettymaps.plot('Porto Alegre', show=False)
# Test plot() with update_preset argument (mocked)
def test_plot_update_preset(monkeypatch):
import prettymaps.draw as draw
monkeypatch.setattr(draw, 'get_gdfs', lambda *a, **k: {'perimeter': get_gdf(), 'building': get_gdf()})
monkeypatch.setattr(draw, 'create_background', lambda *a, **k: (None, 0,0,0,0,0,0))
monkeypatch.setattr(draw, 'draw_layers', lambda *a, **k: None)
monkeypatch.setattr(draw, 'draw_keypoints', lambda *a, **k: None)
monkeypatch.setattr(draw, 'draw_background', lambda *a, **k: None)
monkeypatch.setattr(draw, 'draw_credit', lambda *a, **k: None)
monkeypatch.setattr(draw, 'draw_hillshade', lambda *a, **k: None)
monkeypatch.setattr(draw, 'manage_presets', lambda *a, **k: ({'building': {}}, {}, None, None, None))
result = prettymaps.plot('Porto Alegre', update_preset='default', show=False)
assert hasattr(result, 'geodataframes')
# Test plot() with semantic=True (mocked)
def test_plot_semantic(monkeypatch):
import prettymaps.draw as draw
monkeypatch.setattr(draw, 'get_gdfs', lambda *a, **k: {'perimeter': get_gdf(), 'building': get_gdf()})
monkeypatch.setattr(draw, 'create_background', lambda *a, **k: (None, 0,0,0,0,0,0))
monkeypatch.setattr(draw, 'draw_layers', lambda *a, **k: None)
monkeypatch.setattr(draw, 'draw_keypoints', lambda *a, **k: None)
monkeypatch.setattr(draw, 'draw_background', lambda *a, **k: None)
monkeypatch.setattr(draw, 'draw_credit', lambda *a, **k: None)
monkeypatch.setattr(draw, 'draw_hillshade', lambda *a, **k: None)
result = prettymaps.plot('Porto Alegre', semantic=True, show=False)
assert hasattr(result, 'geodataframes')
# Test plot() with adjust_aspect_ratio=False (mocked)
def test_plot_adjust_aspect_ratio(monkeypatch):
import prettymaps.draw as draw
monkeypatch.setattr(draw, 'get_gdfs', lambda *a, **k: {'perimeter': get_gdf(), 'building': get_gdf()})
monkeypatch.setattr(draw, 'create_background', lambda *a, **k: (None, 0,0,0,0,0,0))
monkeypatch.setattr(draw, 'draw_layers', lambda *a, **k: None)
monkeypatch.setattr(draw, 'draw_keypoints', lambda *a, **k: None)
monkeypatch.setattr(draw, 'draw_background', lambda *a, **k: None)
monkeypatch.setattr(draw, 'draw_credit', lambda *a, **k: None)
monkeypatch.setattr(draw, 'draw_hillshade', lambda *a, **k: None)
result = prettymaps.plot('Porto Alegre', adjust_aspect_ratio=False, show=False)
assert hasattr(result, 'geodataframes')

23
tests/test_app_runs.py Normal file
View File

@@ -0,0 +1,23 @@
import subprocess
import sys
import time
import os
def test_streamlit_app_runs():
# Start the Streamlit app in headless mode
proc = subprocess.Popen(
[sys.executable, "-m", "streamlit", "run", os.path.abspath("app.py"), "--server.headless", "true"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
try:
# Wait a few seconds to see if it crashes
time.sleep(10)
# Check if the process is still running (should be)
assert proc.poll() is None, "Streamlit app crashed on startup"
finally:
proc.terminate()
try:
proc.wait(timeout=5)
except subprocess.TimeoutExpired:
proc.kill()