Component Apps: Mapping with Custom Styles
Important
These recipes only apply to Component App development and will not work for Standard Apps.
Last Updated: January 2026
Style Vector Points with Icons from pytablericons
Note
pytablericons does not come pre-installed with Tethys Platform and can be installed with pip install pytablericons.
View all available icons here and the note on their usage within pytablericons here.
import base64
import math
import numpy as np
from io import BytesIO
from pytablericons import TablerIcons, FilledIcon, OutlineIcon
def get_icon_data_url(icon, size=32, color="black"):
icon_img = TablerIcons.load(icon, size=size, color=color)
buffered = BytesIO()
icon_img.save(buffered, format="PNG")
img_str = base64.b64encode(buffered.getvalue()).decode("utf-8")
data_url = f"data:image/png;base64,{img_str}"
return data_url
@App.page
def points_with_pytablericons(lib):
icon_size = 16
icons = list(FilledIcon)
num_icons = len(icons)
num_rows = num_cols = int(math.sqrt(num_icons))
x_min, x_max, x_spacing = -180, 180, 360/num_rows
y_min, y_max, y_spacing = -90, 90, 180/num_rows
x_coords = np.arange(x_min, x_max + x_spacing, x_spacing)
y_coords = np.arange(y_min, y_max + y_spacing, y_spacing)
X, Y = np.meshgrid(x_coords, y_coords)
X_flat = X.ravel()
Y_flat = Y.ravel()
combinations_xy = np.vstack((X_flat, Y_flat)).T
features = [
lib.Props(
geometry=lib.ol.geom.Point([int(x), int(y)]),
style=lib.ol.Style(
image=lib.ol.style.Icon(
anchor=[0, 0],
anchorXUnits="fraction",
anchorYUnits="pixels",
width=icon_size,
height=icon_size,
src=get_icon_data_url(icons[i], size=icon_size)
)
)
) for i, (x, y) in enumerate(combinations_xy) if i < num_icons
]
return lib.tethys.Display(
lib.tethys.Map(projection="EPSG:4326")(
lib.ol.layer.Vector(
lib.ol.source.Vector(
features=features,
format="olFeature"
)
)
)
)
Style Vector Points with Icons from simplepycons
Note
simplepycons does not come pre-installed with Tethys Platform and can be installed with pip install simplepycons.
View all available icons here and the note on their usage within simplepycons here
import math
import numpy as np
from simplepycons import all_icons
@App.page
def points_with_simplepycons(lib):
icon_size = 16
icon_names = [
x.split('_')[1]
for x in dir(all_icons)
if x.startswith("get_")
][:100] # Limit to 100 unique icons
num_icons = len(icon_names)
num_rows = num_cols = int(math.sqrt(num_icons))
x_min, x_max, x_spacing = -180, 180, 360/num_rows
y_min, y_max, y_spacing = -90, 90, 180/num_rows
x_coords = np.arange(x_min, x_max + x_spacing, x_spacing)
y_coords = np.arange(y_min, y_max + y_spacing, y_spacing)
X, Y = np.meshgrid(x_coords, y_coords)
X_flat = X.ravel()
Y_flat = Y.ravel()
combinations_xy = np.vstack((X_flat, Y_flat)).T
features = [
lib.Props(
geometry=lib.ol.geom.Point([int(x), int(y)]),
style=lib.ol.Style(
image=lib.ol.style.Icon(
anchor=[0, 0],
anchorXUnits="fraction",
anchorYUnits="pixels",
width=icon_size,
height=icon_size,
src=all_icons[icon_names[i]].customize_svg_as_data_url(
fill="blue",
width=str(icon_size),
height=str(icon_size)
)
)
)
) for i, (x, y) in enumerate(combinations_xy) if i < num_icons
]
return lib.tethys.Display(
lib.tethys.Map(projection="EPSG:4326")(
lib.ol.layer.Vector(
lib.ol.source.Vector(
features=features,
format="olFeature"
)
)
)
)
Style Vector Point with Dynamic Icon from pytablericons
Note
pytablericons does not come pre-installed with Tethys Platform and can be installed with pip install pytablericons.
View all available icons here and the note on their usage within pytablericons here.
import base64
from io import BytesIO
from pytablericons import TablerIcons, FilledIcon
def get_icon_data_url(icon, size=32, color="black"):
icon_img = TablerIcons.load(icon, size=size, color=color)
buffered = BytesIO()
icon_img.save(buffered, format="PNG")
img_str = base64.b64encode(buffered.getvalue()).decode("utf-8")
data_url = f"data:image/png;base64,{img_str}"
return data_url
@App.page
def dynamic_icon_with_pytablericons(lib):
icon_index, set_icon_index = lib.hooks.use_state(0)
icon_size = 16
icons = list(FilledIcon)
num_icons = len(icons)
features = [
lib.ol.Feature(
geometry=lib.ol.geom.Point([0, 0]),
style=lib.ol.Style(
image=lib.ol.style.Icon(
anchor=[0, 0],
anchorXUnits="fraction",
anchorYUnits="pixels",
width=icon_size,
height=icon_size,
src=get_icon_data_url(icons[icon_index], size=icon_size)
)
)
)
]
return lib.tethys.Display(
lib.bs.Button(
style=lib.Style(
position="absolute",
top=0,
right="20px",
zIndex=1
),
onClick=lambda _: set_icon_index(icon_index + 1 if icon_index + 1 < num_icons else 0)
)("Swap Icon"),
lib.tethys.Map(
lib.ol.layer.Vector(
lib.ol.source.Vector(
features=features,
format="olFeature"
)
)
)
)
Style Vector Point with Dynamic Icon from simplepycons
Note
simplepycons does not come pre-installed with Tethys Platform and can be installed with pip install simplepycons.
View all available icons here and the note on their usage within simplepycons here
from simplepycons import all_icons
@App.page
def dynamic_icon_with_simplepycons(lib):
icon_index, set_icon_index = lib.hooks.use_state(0)
icon_size = 16
icon_names = [x.split('_')[1] for x in dir(all_icons) if x.startswith("get_")]
num_icons = len(icon_names)
features = [
lib.ol.Feature(
geometry=lib.ol.geom.Point([0, 0]),
style=lib.ol.Style(
image=lib.ol.style.Icon(
anchor=[0, 0],
anchorXUnits="fraction",
anchorYUnits="pixels",
width=icon_size,
height=icon_size,
src=all_icons[icon_names[icon_index]].customize_svg_as_data_url(
fill="black",
width=str(icon_size),
height=str(icon_size)
)
)
)
)
]
return lib.tethys.Display(
lib.bs.Button(
style=lib.Style(
position="absolute",
top=0,
right="20px",
zIndex=1
),
onClick=lambda _: set_icon_index(icon_index + 1 if icon_index + 1 < num_icons else 0)
)("Swap Icon"),
lib.tethys.Map(
lib.ol.layer.Vector(
lib.ol.source.Vector(
features=features,
format="olFeature"
)
)
)
)
GeoJSON Features with Dynamic Style
@App.page
def geojson_with_dynamic_style(lib):
fill_color, set_fill_color = lib.hooks.use_state("#ff0000")
stroke_color, set_stroke_color = lib.hooks.use_state("#000000")
return lib.tethys.Display(
lib.html.div(
style=lib.Style(
position="absolute",
top=10,
right=20,
zIndex=1
)
)(
lib.bs.FormLabel(htmlFor="fill-color")("Fill Color"),
lib.bs.FormControl(
id_="fill-color",
type_="color",
defaultValue=fill_color,
onChange=lambda e: set_fill_color(e.target.value)
),
lib.bs.FormLabel(htmlFor="stroke-color")("Stroke Color"),
lib.bs.FormControl(
id_="stroke-color",
type_="color",
defaultValue=stroke_color,
onChange=lambda e: set_stroke_color(e.target.value),
),
),
lib.tethys.Map(
lib.ol.layer.Vector(
style=lib.ol.style.Style(
stroke=lib.ol.style.Stroke(
color=stroke_color,
width=1
),
fill=lib.ol.style.Fill(
color=fill_color
)
)
)(
lib.ol.source.Vector(
options=lib.Props(
url="https://d2ad6b4ur7yvpq.cloudfront.net/naturalearth-3.3.0/ne_50m_urban_areas.geojson",
format_="GeoJSON"
)
)
)
)
)
Style GeoJSON with Unique Values Styler
@App.page
def geojson_unique_styler(lib):
return lib.tethys.Display(
lib.tethys.Map(
lib.ol.layer.Vector(
style=lib.ol.style.Styler(
method="unique_values",
fields=["scalerank"],
values=[
(
1,
lib.ol.style.Style(
stroke=lib.ol.style.Stroke(
color="purple",
width=1
),
fill=lib.ol.style.Fill(
color="white"
),
text=lib.ol.style.Text(
text="%{$feature.scalerank}",
fill=lib.ol.style.Fill(color="orange"),
stroke=lib.ol.style.Stroke(color="#f1234567", width=2),
),
)
),
(
2,
lib.ol.style.Style(
stroke=lib.ol.style.Stroke(
color="#459302",
width=1
),
fill=lib.ol.style.Fill(
color="#18181818"
),
text=lib.ol.style.Text(
text="%{$feature.scalerank}",
fill=lib.ol.style.Fill(color="orange"),
stroke=lib.ol.style.Stroke(color="#f1234567", width=2),
),
)
),
(
3,
lib.ol.style.Style(
stroke=lib.ol.style.Stroke(
color="#93102849",
width=1
),
fill=lib.ol.style.Fill(
color="#f930efd3"
),
text=lib.ol.style.Text(
text="%{$feature.scalerank}",
fill=lib.ol.style.Fill(color="orange"),
stroke=lib.ol.style.Stroke(color="#f1234567", width=2),
),
)
)
],
default=lib.ol.style.Style(
stroke=lib.ol.style.Stroke(
color="#000000",
width=1
),
fill=lib.ol.style.Fill(
color="#ff0000"
),
text=lib.ol.style.Text(
text="%{$feature.scalerank}",
fill=lib.ol.style.Fill(color="blue"),
stroke=lib.ol.style.Stroke(color="white", width=2),
),
)
)
)(
lib.ol.source.Vector(
options=lib.Props(
url="https://d2ad6b4ur7yvpq.cloudfront.net/naturalearth-3.3.0/ne_50m_urban_areas.geojson",
format_="GeoJSON"
)
)
)
)
)