The Style Pipeline
The Style Pipeline is one of the core features of AurOpenlayers. It allows you to separate your business logic (what a feature represents) from its visual representation (how it looks).
Instead of manually creating OpenLayers Style objects every time a model changes, you define a declarative pipeline: Model → Base Options → State Patches → Render Function.
1. Defining the Base Style
The starting point of the pipeline is the base function. It takes your domain model and returns a "Style Options" object—a simple POJO (Plain Old JavaScript Object) that describes the visual properties.
// Define a type for your visual options
type PointStyleOptions = {
color: string;
radius: number;
label: string;
};
// In your Layer Descriptor:
style: {
base: (model: MapPoint): PointStyleOptions => ({
color: '#0ea5e9',
radius: 7,
label: model.name,
}),
// ... render function
}
2. Adding Interactive States
Interactive states like HOVER, SELECTED, or DRAG are applied as patches over your base style. When a feature enters a state, the framework automatically merges the state's properties into the options object.
You can provide a partial object or a function that calculates the patch based on previous values.
style: {
base: (model) => ({ color: '#1d4ed8', radius: 7 }),
states: {
// Static patch for selected items
SELECTED: {
color: '#f97316',
radius: 9
},
// Dynamic patch using the previous options
DRAG: (prev) => ({
color: '#dc2626',
radius: prev.radius + 3
})
}
}
3. Using Map Context (LOD Styles)
Sometimes styles need to change based on the map's zoom level or resolution (Level of Detail). All style functions receive a second argument: StyleView.
style: {
base: (model, { zoom }) => ({
color: '#0ea5e9',
radius: zoom && zoom > 12 ? 10 : 5, // Grow points when zooming in
label: zoom && zoom > 10 ? model.name : '' // Hide labels on low zoom
})
}
4. The Render Function
The final step is the render function. This is the only place where you instantiate OpenLayers classes (Style, Fill, Stroke, Icon). It receives the final, merged options object.
By separating the rendering logic, you can easily swap visual implementations without touching your business logic.
style: {
// ... base and states
render: (opts: PointStyleOptions) => {
return new Style({
image: new CircleStyle({
radius: opts.radius,
fill: new Fill({ color: opts.color }),
stroke: new Stroke({ color: '#ffffff', width: 2 }),
}),
text: new Text({
text: opts.label,
offsetY: 20,
font: '600 12px "Inter", sans-serif',
}),
});
}
}
Complete Walkthrough: Interactive Route Point
In this example, we combine all the steps to create a point that changes color when selected and grows when dragged.
Step 1: Interface Design
Define what your style needs to know.
type MyPointStyle = { color: string; size: number; text: string };
Step 2: Configure the Descriptor
const layerDescriptor: VectorLayerDescriptor<MapPoint, Point, MyPointStyle> = {
id: 'points-layer',
feature: {
id: (m) => m.id,
geometry: { /* ... geometry logic ... */ },
style: {
// 1. Base logic
base: (model) => ({
color: '#2563eb',
size: 8,
text: model.name
}),
// 2. State-based overrides
states: {
SELECTED: { color: '#f97316', size: 10 },
DRAG: { color: '#16a34a', size: 12 }
},
// 3. Final rendering to OpenLayers
render: (opts) => new Style({
image: new CircleStyle({
radius: opts.size,
fill: new Fill({ color: opts.color })
}),
text: new Text({ text: opts.text })
})
}
}
};
Step 3: Triggering States
States are usually triggered by interactions. For example, if you use the translate interaction, the framework can automatically apply the DRAG state:
interactions: {
translate: {
state: 'DRAG', // Automatically applies the DRAG style patch while moving
cursor: 'grabbing'
}
}
Summary Table
| Pipeline Stage | Input | Output | Purpose |
| :--- | :--- | :--- | :--- |
| base | (model, view) | Options | Defines the default visual properties. |
| states | (prev, view) | Patch<Options> | Modifies properties for specific states (Hover, Drag, etc). |
| render | (options) | ol/Style | Converts generic options into OpenLayers objects. |