Skip to content

Commit

Permalink
Merge pull request #61 from chrieke/enable_simpler_landcover_customiz…
Browse files Browse the repository at this point in the history
…ation

Enable simpler landcover customization
  • Loading branch information
chrieke authored Nov 30, 2024
2 parents 8fd831c + c3a8431 commit 3f11418
Show file tree
Hide file tree
Showing 6 changed files with 142 additions and 32 deletions.
42 changes: 38 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ pip install prettymapp

**Define the area, download and plot the OSM data:**

You can select from 4 [predefined styles](prettymapp/settings.py#L35): `Peach`, `Auburn`, `Citrus` and `Flannel`.

```python
from prettymapp.geo import get_aoi
from prettymapp.osm import get_osm_geometries
Expand All @@ -63,7 +65,7 @@ df = get_osm_geometries(aoi=aoi)
fig = Plot(
df=df,
aoi_bounds=aoi.bounds,
draw_settings=STYLES["Peach"]
draw_settings=STYLES["Peach"],
).plot_all()

fig.savefig("map.jpg")
Expand All @@ -79,6 +81,38 @@ aoi_bounds = df.total_bounds
...
```

To customize the map appearance, use the additional arguments of the [`Plot`](plotting.py#L36) class (e.g. `shape`,
`contour_width` etc.). Check the preconfigured [styles](prettymapp/settings.py#L35) and
webapp [examples](streamlit-prettymapp/examples.json) for inspiration.
**Customize styles & layers**

Edit the `draw_settings` input to create your own custom styles! The map layout can be further customized with the additional arguments of the [`Plot`](plotting.py#L36) class (e.g. `shape`, `contour_width` etc.). Check the webapp [examples](streamlit-prettymapp/examples.json) for inspiration.

```
from prettymapp.settings import STYLES
custom_style = STYLES["Peach"].copy()
custom_style["urban"] = {
"cmap": ["#3452eb"],
"ec": "#E9724C",
"lw": 0.2,
"zorder": 4,
}
fig = Plot(
df=df,
aoi_bounds=aoi.bounds,
draw_settings=custom_style,
shape="circle",
contour_width=0,
).plot_all()
```

You can also customize the selection of OSM landcover classes that should be displayed.

```
from prettymapp.settings import LANDCOVER_CLASSES
custom_lc_classes = LANDCOVER_CLASSES.copy()
custom_lc_classes["urban"]["building"] = False
df = get_osm_geometries(aoi=aoi, landcover_classes=custom_lc_classes)
```
73 changes: 65 additions & 8 deletions prettymapp/example_notebook.ipynb

Large diffs are not rendered by default.

3 changes: 1 addition & 2 deletions prettymapp/geo.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,7 @@ def get_aoi(
df = GeoDataFrame(
DataFrame([0], columns=["id"]), crs="EPSG:4326", geometry=[Point(lon, lat)]
)
utm_crs = df.estimate_utm_crs()
df = df.to_crs(utm_crs)
df = df.to_crs(df.estimate_utm_crs())
df.geometry = df.geometry.buffer(radius)
df = df.to_crs(crs=4326)
poly = df.iloc[0].geometry
Expand Down
32 changes: 23 additions & 9 deletions prettymapp/osm.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,21 @@
from shapely.geometry import Polygon

from prettymapp.geo import explode_multigeometries
from prettymapp.settings import LC_SETTINGS
from prettymapp.settings import LANDCOVER_CLASSES

settings.use_cache = True
settings.log_console = False


def get_osm_tags():
def get_osm_tags(landcover_classes: dict = LANDCOVER_CLASSES):
"""
Get relevant OSM tags for use with prettymapp
Args:
landcover_classes: Landcover selection settings, defaults to prettymapp.settings.LANDCOVER_CLASSES
"""
tags: dict = {}
for d in LC_SETTINGS.values(): # type: ignore
for d in landcover_classes.values(): # type: ignore
for k, v in d.items(): # type: ignore
try:
tags.setdefault(k, []).extend(v)
Expand All @@ -27,9 +30,16 @@ def get_osm_tags():
return tags


def cleanup_osm_df(df: GeoDataFrame, aoi: Union[Polygon, None] = None) -> GeoDataFrame:
def cleanup_osm_df(
df: GeoDataFrame, aoi: Union[Polygon, None] = None, landcover_classes: dict = LANDCOVER_CLASSES
) -> GeoDataFrame:
"""
Cleanup of queried osm geometries to relevant level for use with prettymapp
Args:
df: GeoDataFrame with queried OSM geometries
aoi: Optional geographic boundary to filter elements
landcover_classes: Landcover selection settings, defaults to prettymapp.settings.LANDCOVER_CLASSES
"""
df = df.droplevel(level=0)
df = df[~df.geometry.geom_type.isin(["Point", "MultiPoint"])]
Expand All @@ -38,7 +48,7 @@ def cleanup_osm_df(df: GeoDataFrame, aoi: Union[Polygon, None] = None) -> GeoDat
df = explode_multigeometries(df)

df["landcover_class"] = None
for lc_class, osm_tags in LC_SETTINGS.items():
for lc_class, osm_tags in landcover_classes.items():
tags_in_columns = list(set(osm_tags.keys()).intersection(list(df.columns))) # type: ignore
mask_lc_class = df[tags_in_columns].notna().sum(axis=1) != 0
# Remove mask elements that belong to other subtag
Expand All @@ -60,30 +70,34 @@ def cleanup_osm_df(df: GeoDataFrame, aoi: Union[Polygon, None] = None) -> GeoDat
return df


def get_osm_geometries(aoi: Polygon) -> GeoDataFrame:
def get_osm_geometries(
aoi: Polygon, landcover_classes: dict = LANDCOVER_CLASSES
) -> GeoDataFrame:
"""
Query OSM features within a polygon geometry.
Args:
aoi: Polygon geometry query boundary.
landcover_classes: Landcover selection settings, defaults to prettymapp.settings.LANDCOVER_CLASSES
"""
tags = get_osm_tags()
tags = get_osm_tags(landcover_classes=landcover_classes)
df = features_from_polygon(polygon=aoi, tags=tags)
df = cleanup_osm_df(df, aoi)
return df


def get_osm_geometries_from_xml(
filepath: Union[str, Path], aoi: Union[Polygon, None] = None
filepath: Union[str, Path], aoi: Union[Polygon, None] = None, landcover_classes: dict = LANDCOVER_CLASSES
) -> GeoDataFrame:
"""
Query OSM features in an OSM-formatted XML file.
Args:
filepath: path to file containing OSM XML data
aoi: Optional geographic boundary to filter elements
landcover_classes: Landcover selection settings, defaults to prettymapp.settings.LANDCOVER_CLASSES
"""
tags = get_osm_tags()
tags = get_osm_tags(landcover_classes=landcover_classes)
df = features_from_xml(filepath, polygon=aoi, tags=tags)
df = cleanup_osm_df(df, aoi)
return df
11 changes: 9 additions & 2 deletions prettymapp/plotting.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from typing import Tuple, List
from dataclasses import dataclass

from dataclasses import field
from geopandas.plotting import _plot_polygon_collection, _plot_linestring_collection
from geopandas import GeoDataFrame
import numpy as np
Expand All @@ -12,7 +13,7 @@
from matplotlib.patches import Ellipse
import matplotlib.patheffects as PathEffects

from prettymapp.settings import STREETS_WIDTH
from prettymapp.settings import STREETS_WIDTH, STYLES


def adjust_lightness(color: str, amount: float = 0.5) -> Tuple[float, float, float]:
Expand All @@ -34,21 +35,27 @@ def adjust_lightness(color: str, amount: float = 0.5) -> Tuple[float, float, flo

@dataclass
class Plot:
"""
Main plotting class for prettymapp.
"""
df: GeoDataFrame
aoi_bounds: List[
float
] # Not df bounds as could lead to weird plot shapes with unequal geometry distribution.
draw_settings: dict
draw_settings: dict = field(default_factory=lambda: STYLES["Peach"])
# Map layout settings
shape: str = "circle"
contour_width: int = 0
contour_color: str = "#2F3537"
# Optional map text settings e.g. to display location name
name_on: bool = False
name: str = "some name"
font_size: int = 25
font_color: str = "#2F3737"
text_x: int = 0
text_y: int = 0
text_rotation: int = 0
# Map background settings
bg_shape: str = "rectangle"
bg_buffer: int = 2
bg_color: str = "#F2F4CB"
Expand Down
13 changes: 6 additions & 7 deletions prettymapp/settings.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
LC_SETTINGS = {
LANDCOVER_CLASSES = {
"urban": {"building": True, "landuse": ["construction", "commercial"]},
"water": {
"natural": ["water", "bay"],
Expand Down Expand Up @@ -30,10 +30,9 @@
"other": {"amenity": ["parking"], "man_made": ["pier"], "highway": ["pedestrian"]},
}

# Macau peach
# Barcelona auburn
# Contains drawing settings
STYLES = {
"Peach": {
"Peach": { # e.g. Macau
"urban": {
"cmap": ["#FFC857", "#E9724C", "#C5283D"],
"ec": "#2F3737",
Expand All @@ -53,7 +52,7 @@
"streets": {"fc": "#2F3737", "zorder": 3},
"other": {"fc": "#F2F4CB", "ec": "#2F3737", "lw": 1, "zorder": 3},
},
"Auburn": {
"Auburn": { # e.g. Barcelona
"urban": {
"cmap": ["#433633", "#FF5E5B", "#FF5E5B"],
"ec": "#2F3737",
Expand All @@ -80,7 +79,7 @@
"streets": {"fc": "#2F3737", "zorder": 4},
"other": {"fc": "#F2F4CB", "ec": "#2F3737", "lw": 1, "zorder": 3},
},
"Citrus": {
"Citrus": { # e.g. Würzburg
"urban": {
"cmap": ["#FFFF3F", "#F4D58D", "#F5CB5C"],
"ec": "#2F3737",
Expand All @@ -105,7 +104,7 @@
"streets": {"fc": "#FFFFFF", "zorder": 4},
"other": {"fc": "#EAE2B7", "ec": "#2F3737", "lw": 1, "zorder": 3},
},
"Flannel": {
"Flannel": { # e.g. Heerhugowaard
"urban": {
"cmap": ["#433633", "#FF5E5B", "#FF5E5B"],
"ec": "#2F3737",
Expand Down

0 comments on commit 3f11418

Please sign in to comment.