Creating Styles
Style architecture
Elzar uses a hierarchical approach where styles are defined as Python dictionaries mapping selector paths to StyleElement objects.
"""StyleTree with hierarchical selector paths."""
from map_style.generator.mapbox_style import LinePaint
from map_style.generator.styler import StyleElement, StyleTree
# Define styles with selector paths
styles: dict[str, StyleElement] = {
"road": StyleElement(
paint=LinePaint(line_color="#ffffff"),
min_zoom=5,
),
"road.highway": StyleElement(
paint=LinePaint(line_color="#ffcc00", line_width=4),
),
"road.highway.motorway": StyleElement(
paint=LinePaint(line_width=6),
),
}
# Create style tree
tree = StyleTree(styles)
Style inheritance
Styles inherit from parent selectors. When resolving road.highway.motorway:
roadstyles apply first (base road styling)road.highwaystyles override (highway-specific)road.highway.motorwaystyles override (motorway-specific)
graph TD
A[road] --> B[road.highway]
A --> C[road.primary]
A --> D[road.secondary]
B --> E[road.highway.motorway]
B --> F[road.highway.trunk]
StyleElement
StyleElement defines the visual properties for a selector:
"""StyleElement fields: paint, layout, zoom, and metadata."""
from map_style.generator.mapbox_style import LineLayout, LinePaint
from map_style.generator.styler import StyleElement
element = StyleElement(
paint=LinePaint(), # Paint properties (colors, widths)
layout=LineLayout(), # Layout properties (caps, joins)
min_zoom=10, # Minimum zoom level
max_zoom=18, # Maximum zoom level
metadata={"category": "roads"}, # Custom metadata
)
Paint classes
Different geometry types use different paint classes:
| Geometry | Paint Class | Layout Class |
|---|---|---|
| Line | LinePaint |
LineLayout |
| Polygon | FillPaint |
FillLayout |
| Symbol | SymbolPaint |
SymbolLayout |
| Circle | CirclePaint |
CircleLayout |
| Background | BackgroundPaint |
BaseLayout |
| Fill Extrusion | FillExtrusionPaint |
BaseLayout |
Example: road style
"""Road paint and layout styles with zoom-dependent widths."""
from map_style.generator.mapbox_style import LineLayout, LinePaint
from map_style.generator.styler import StyleElement
road_styles: dict[str, StyleElement] = {
# Base road style
"road": StyleElement(
paint=LinePaint(
line_color="#ffffff",
line_opacity=1.0,
),
layout=LineLayout(
line_cap="round",
line_join="round",
),
),
# Highway styling
"road.highway": StyleElement(
paint=LinePaint(
line_color="#ffcc00",
line_width={"stops": [[10, 2], [14, 6], [18, 12]]},
),
),
# Highway casing (outline)
"road.highway_casing": StyleElement(
paint=LinePaint(
line_color="#e6a800",
line_width={"stops": [[10, 4], [14, 8], [18, 14]]},
line_gap_width=0,
),
),
}
Example: label style
"""Symbol label styles for place names."""
from map_style.generator.mapbox_style import SymbolLayout, SymbolPaint
from map_style.generator.styler import StyleElement
label_styles: dict[str, StyleElement] = {
"label.place": StyleElement(
paint=SymbolPaint(
text_color="#333333",
text_halo_color="#ffffff",
text_halo_width=1.5,
),
layout=SymbolLayout(
text_field="${wgs_localization}",
text_font=["Noto Sans Regular"],
text_size=14,
text_anchor="center",
),
),
"label.place.city": StyleElement(
layout=SymbolLayout(
text_size=18,
text_font=["Noto Sans Bold"],
),
min_zoom=4,
max_zoom=14,
),
}
Creating a complete style
"""Complete style class with render method."""
from map_style.generator import SourceLayerRef
from map_style.generator.data import Layer
from map_style.generator.mapbox_style import Style
from map_style.generator.render import render_mapbox_style
from map_style.generator.styler import StyleElement, StyleTree
class MyStyle:
def __init__(self) -> None:
self.styles: dict[str, StyleElement] = {
# Define all your styles here
}
self.layers: list[Layer] = [
# Define layer data
]
self.sources: dict[str, SourceLayerRef] = {
# Define tile sources
}
def render(self) -> tuple[Style, set[str]]:
style_tree = StyleTree(self.styles)
return render_mapbox_style(
self.layers,
style_tree,
self.sources,
)
Best practices
-
Use meaningful selector paths:
road.highway.motorwayis clearer thanmotorway -
Define base styles first: Start with general styles, then specialize
-
Use zoom-dependent values: Adjust line widths and sizes for different zoom levels
line_width = NumberInterpolate(stops=[(10, 1.0), (14, 3.0), (18, 6.0)]) -
Group related styles: Keep road styles, label styles, and POI styles in separate modules
-
Document color choices: Use a central colors module
# colors.py HIGHWAY: str = "#ffcc00" HIGHWAY_CASING: str = "#e6a800"