The Declarative Map Schema
The Map Schema is the central contract of your map. Instead of writing imperative OpenLayers code to create layers, features, and event listeners, you define a structure that tells the engine what should be on the map and how it should behave.
This guide walks you through building a declarative schema, from defining basic layers to handling complex bidirectional geometry updates.
1. Defining the Basic Layer Structure
Every map configuration starts with the schema.layers array. Each layer is defined by a VectorLayerDescriptor, which acts as a blueprint for how your business models (data) translate into map features.
import { VectorLayerDescriptor } from 'aur-openlayers';
const mapConfig: MapHostConfig = {
schema: {
layers: [
{
id: 'delivery-points',
feature: {
// Tell the engine how to get a unique ID from your model
id: (model: DeliveryPoint) => model.uuid,
// ... geometry and style
}
}
]
}
};
2. Connecting the Business Model (Geometry Mapping)
One of the most powerful features of the schema is the bidirectional geometry sync. You define two functions in the geometry block:
fromModel: Converts your business object into an OpenLayers geometry.applyGeometryToModel: Updates your business object when the geometry changes on the map (e.g., after a drag/translate interaction).
geometry: {
// Model -> Map
fromModel: (model: DeliveryPoint) =>
new Point(fromLonLat([model.lng, model.lat])),
// Map -> Model (triggered by interactions like 'translate')
applyGeometryToModel: (prev: DeliveryPoint, geom: Point) => {
const [lng, lat] = toLonLat(geom.getCoordinates());
return { ...prev, lng, lat }; // Return a new immutable object
}
}
3. Declarative Styling and States
Styles in aur-openlayers are split into two parts: Base Options (pure data) and Render (OpenLayers objects). This separation allows the framework to efficiently calculate style changes and handle "states" like Hover or Selection.
Defining the Style Contract
The base function calculates a set of parameters, and the render function turns them into a real ol/Style.
style: {
// 1. Calculate style parameters from the model
base: (model: DeliveryPoint) => ({
color: '#2563eb',
radius: 7,
label: model.name,
}),
// 2. Define state-specific overrides (patches)
states: {
SELECTED: () => ({ color: '#f97316', radius: 10 }),
HOVER: () => ({ radius: 9 })
},
// 3. Convert parameters into an OpenLayers Style object
render: (opts) => new Style({
image: new CircleStyle({
radius: opts.radius,
fill: new Fill({ color: opts.color }),
}),
text: new Text({ text: opts.label })
})
}
4. Enabling Interactions
Interactions are described directly within the layer feature. For example, if you want to allow a user to drag a point and update your data, you simply add a translate configuration.
interactions: {
translate: {
cursor: 'grab', // Cursor style while hovering
state: 'DRAGGING', // Apply this style state while moving
enabled: () => true, // Can be a function for conditional logic
// Optional: refine which items can be picked
pickTarget: ({ candidates }) => candidates[0],
onStart: () => console.log('Started moving'),
onEnd: () => console.log('Finished moving')
}
}
5. Using the Schema in a Component
Once your config is defined, pass it to the <mff-map-host> component. To populate the map with data, wait for the ready event to get the MapContext and use the layer API.
@Component({
template: `
<mff-map-host [config]="mapConfig" (ready)="onReady($event)"></mff-map-host>
`
})
export class MyMapComponent {
readonly mapConfig = MY_SCHEMA_CONFIG;
onReady(ctx: MapContext) {
// Access the layer by the ID defined in the schema
const layerApi = ctx.layers['delivery-points'];
// Set your data models
layerApi.setModels(this.myData);
// Listen for changes initiated by map interactions
layerApi.onModelsChanged((changes) => {
changes.forEach(change => {
console.log(`Point ${change.next.id} moved to:`, change.next.lat, change.next.lng);
});
});
}
}
Summary Table: Schema Properties
| Property | Type | Description |
| :--- | :--- | :--- |
| id | string | Unique identifier for the layer. |
| feature.id | (model) => Id | Mapping to the model's primary key. |
| feature.geometry | GeometryDescriptor | Logic for model <-> geometry synchronization. |
| feature.style.base | (model) => Options | Default style parameters based on data. |
| feature.style.states| Record<string, Patch> | Style overrides for states like SELECTED or HOVER. |
| feature.interactions| InteractionConfig | Setup for translate, select, modify, etc. |