Generator Reference
The generator package provides the core rendering engine for Elzar.
Modules
render
Style rendering functions.
elzar.generator.render
Render map style layers to MapLibre GL style format.
This module provides functionality to convert internal layer representations
to MapLibre GL JSON style format, including icon resolution from templates.
LayerRenderer
Source code in elzar/generator/render.py
| def __init__(self, children: list[Node]) -> None:
self.children = children
|
render_mapbox_style
render_mapbox_style(data_layers, style_tree, sources, style_name='{style_name}')
Source code in elzar/generator/render.py
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197 | def render_mapbox_style(
data_layers: list[Layer],
style_tree: StyleTree,
sources: dict[str, SourceLayerRef],
style_name: str = "{style_name}",
) -> tuple[Style, set[str]]:
used_icons: set[str] = set()
obfuscate_layer_id = False
mb_layers: list[Any] = []
for layer in data_layers:
resolved_styles: list[StyleElement] = style_tree.resolve(layer.id)
if resolved_styles == []:
continue
resolved_classes = type_obj_map.get(layer.geometry_type)
if resolved_classes is not None:
resolved_style_data: dict[str, Any] = {}
resolved_layout_data: dict[str, Any] = {}
min_zoom = None
max_zoom = None
# Start with StyleElement metadata, then override with Layer's values
resolved_metadata: dict[str, Any] = {}
for resolved_style in resolved_styles:
paint = resolved_style.paint
layout = resolved_style.layout
if paint:
resolved_style_data |= paint.model_dump()
if layout:
resolved_layout_data |= layout.model_dump()
if resolved_style.min_zoom is not None:
min_zoom = resolved_style.min_zoom
if resolved_style.max_zoom is not None:
max_zoom = resolved_style.max_zoom
if resolved_style.metadata is not None:
resolved_metadata |= resolved_style.metadata
# Layer's featureType and elementTypes take precedence over StyleElement
resolved_metadata["elementTypes"] = layer.element_types
resolved_metadata["featureType"] = layer.feature_type
resolved_paint = resolved_classes.paint_class.model_validate(resolved_style_data)
resolved_layout = resolved_classes.layout_class.model_validate(resolved_layout_data)
source = sources.get(layer.id.split(".")[0])
if isinstance(resolved_layout, SymbolLayout):
used_icons.update(get_used_icons(layer, resolved_layout))
resolved_filter = ExpressionSet(layer.filter).resolve() if layer.filter else None
if obfuscate_layer_id:
layer_id = sha256(layer.id.encode("utf-8")).hexdigest()
else:
layer_id = layer.id
# Build layer kwargs - some layer types don't support all fields
layer_kwargs: dict[str, Any] = {
"id": layer_id,
"paint": resolved_paint,
"metadata": resolved_metadata or None,
}
# Only add min/max zoom if set
if min_zoom is not None:
layer_kwargs["min_zoom"] = min_zoom
if max_zoom is not None:
layer_kwargs["max_zoom"] = max_zoom
# Raster, Background, and HillShade layers don't have source_layer
if layer.geometry_type not in (GeometryType.BACKGROUND,):
layer_kwargs["source"] = source.source if source else None
# Only vector layers have source_layer and filter
if layer.geometry_type not in (
GeometryType.BACKGROUND,
GeometryType.RASTER,
):
layer_kwargs["source_layer"] = source.layer if source else None
layer_kwargs["filter"] = resolved_filter
layer_kwargs["layout"] = resolved_layout
resolved_layer = resolved_classes.layer_class(**layer_kwargs)
mb_layers.append(resolved_layer)
mapbox_style = Style(
id=style_name,
name=style_name,
layers=mb_layers,
metadata=StyleMetadata(featureTypeAliases={"transit.station": "poi.transit.station"}),
sprite=None,
glyphs=None,
sources={},
)
return mapbox_style, used_icons
|
get_used_icons
get_used_icons(layer, resolved_layout)
Source code in elzar/generator/render.py
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229 | def get_used_icons(layer: Layer, resolved_layout: SymbolLayout) -> set[str]:
resolved_icon = resolved_layout.icon_image
icons: set[str] = set()
if resolved_icon is not None:
# Skip MapLibre expressions (lists) or non-string types - icons are resolved at runtime
if not isinstance(resolved_icon, str):
return icons
if layer.filter and "{" in resolved_icon:
filter_context = ExpressionSet(layer.filter).get_filter_map()
template_context = re.match(r".*{(?P<context_key>.+)}.*", resolved_icon)
if template_context:
template_key = template_context.group("context_key")
value = filter_context.get(template_key)
if value is None:
# Template key not extractable from filter - skip icon resolution
return icons
if isinstance(value, list):
for v in value:
icons.add(resolved_icon.replace(f"{{{template_key}}}", v))
else:
icons.add(resolved_icon)
else:
icons.add(resolved_icon)
return icons
|
styler
Style tree implementation.
elzar.generator.styler
Style tree for hierarchical style resolution.
This module provides a tree structure for organizing and resolving styles
based on dot-separated selector paths (e.g., 'road.highway.motorway').
StyleElement
StyleElement(paint=None, layout=None, min_zoom=None, max_zoom=None, metadata=None)
Source code in elzar/generator/styler.py
36
37
38
39
40
41
42
43
44
45
46
47
48 | def __init__(
self,
paint: BaseModel | None = None,
layout: BaseModel | None = None,
min_zoom: float | None = None,
max_zoom: float | None = None,
metadata: dict[str, Any] | None = None,
) -> None:
self.paint = paint
self.layout = layout
self.min_zoom = min_zoom
self.max_zoom = max_zoom
self.metadata = metadata
|
__or__
Merge two StyleElements. other fields override self where explicitly set.
Source code in elzar/generator/styler.py
50
51
52
53
54
55
56
57
58
59
60 | def __or__(self, other: StyleElement) -> StyleElement:
"""Merge two StyleElements. ``other`` fields override ``self`` where explicitly set."""
paint = _merge_models(self.paint, other.paint)
layout = _merge_models(self.layout, other.layout)
return StyleElement(
paint=paint,
layout=layout,
min_zoom=other.min_zoom if other.min_zoom is not None else self.min_zoom,
max_zoom=other.max_zoom if other.max_zoom is not None else self.max_zoom,
metadata={**(self.metadata or {}), **(other.metadata or {})} if self.metadata or other.metadata else None,
)
|
StyleNode
Source code in elzar/generator/styler.py
| def __init__(self, name: str) -> None:
self.name = name
self.elements = []
self.children: dict[str, StyleNode] = {}
|
StyleTree
Source code in elzar/generator/styler.py
| def __init__(self, style: dict[str, StyleElement]) -> None:
self.root = StyleNode("ROOT")
for selector_path, element in style.items():
if element.metadata is not None:
element.metadata["featureType"] = selector_path
self.add_node(selector_path, element)
|
data
Layer and data definitions.
elzar.generator.data
Data structures for map style layer definitions.
This module defines the core data types for representing map layers,
including geometry types, render groups, and specialized road/rail nodes.
Layer
dataclass
Layer(id, geometry_type, filter=None, feature_type=None, element_types=None)
DataNode
DataNode(id, filter=None, geometry_type=BACKGROUND, render_group=TOP, feature_type=_FEATURE_TYPE_DEFAULT, element_types=None)
Bases: Node
Source code in elzar/generator/data.py
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310 | def __init__(
self,
id: str,
filter: Q | None = None,
geometry_type: GeometryType = GeometryType.BACKGROUND,
render_group: InjectionsNames = InjectionsNames.TOP,
feature_type: str | None | object = _FEATURE_TYPE_DEFAULT,
element_types: list[str] | None = None,
) -> None:
self.id = id
self.filter = filter
self.geometry_type = geometry_type
self.render_group = render_group
# feature_type=None means explicitly null, _FEATURE_TYPE_DEFAULT means use layer id
self.feature_type: str | None = id if feature_type is _FEATURE_TYPE_DEFAULT else feature_type # type: ignore[assignment]
self.element_types = element_types
|
render
Renders the layer in the stack
Source code in elzar/generator/data.py
312
313
314
315
316
317
318
319
320
321
322
323 | def render(self, stack: Stack[InjectionsNames]) -> None:
"""Renders the layer in the stack"""
stack.add_layer_before(
self.render_group,
Layer(
self.id,
filter=self.filter,
feature_type=self.feature_type,
geometry_type=self.geometry_type,
element_types=self.element_types,
),
)
|
Stack
Bases: Node, Generic[T]
Source code in elzar/generator/data.py
| def __init__(self, render_groups: type[T]) -> None:
self.layers = [InjectionPointLayer(id=render_group.value) for render_group in render_groups]
|
get_layers
Returns only renderable layers
Source code in elzar/generator/data.py
| def get_layers(self) -> list[Layer]:
"""Returns only renderable layers"""
return list(filter(lambda layer: not isinstance(layer, InjectionPointLayer), self.layers))
|
render
Render this node to the layer stack. Override in subclasses.
Source code in elzar/generator/data.py
| def render(self, stack: Stack[InjectionsNames]) -> None:
"""Render this node to the layer stack. Override in subclasses."""
pass
|
filters
Filter expression handling.
elzar.generator.filters
Convert Q filter expressions to MapLibre GL filter format.
This module transforms Django-style Q expressions into MapLibre GL JSON
filter expressions for use in map style layer definitions.
ExpressionSet
Source code in elzar/generator/filters.py
| def __init__(self, q: Q | None = None) -> None:
self.expr = q or Q()
|