Tutorial: Interactive Polygons
In this tutorial, you will learn how to implement interactive polygons with dynamic labels and modification support using the declarative approach of AurOpenlayers. We will build a "Zone Manager" where users can view areas, see their names, and reshape them directly on the map.
1. Define the Polygon Model
First, define the business model for your polygon. In AurOpenlayers, the map is a reflection of your data, so we start with a plain TypeScript object.
export interface AreaZone {
id: string;
name: string;
// Array of rings, where each ring is an array of [lng, lat]
path: number[][][];
color: string;
}
2. Configure the Layer Descriptor
The VectorLayerDescriptor tells the framework how to turn your AreaZone objects into OpenLayers features.
Geometry Mapping
We need to handle the conversion between your model's coordinates and OpenLayers geometry.
import { Polygon } from 'ol/geom';
import { fromLonLat, toLonLat } from 'ol/proj';
const zoneDescriptor: VectorLayerDescriptor<AreaZone, Polygon, any> = {
id: 'zones-layer',
feature: {
id: (m) => m.id,
geometry: {
// Model -> Map
fromModel: (m) => new Polygon(m.path.map(ring => ring.map(coord => fromLonLat(coord)))),
// Map -> Model (Syncs changes back after modification)
applyGeometryToModel: (prev, geom) => {
const coordinates = geom.getCoordinates().map(ring =>
ring.map(coord => toLonLat(coord))
);
return { ...prev, path: coordinates };
}
},
// ... styles and interactions go here
}
};
3. Adding Dynamic Labels and Styles
To show labels and handle different states (like hovering), we define the style property. The base function prepares data, and the render function creates the OpenLayers style object.
style: {
base: (model: AreaZone) => ({
fillColor: model.color + '44', // Transparent fill
strokeColor: model.color,
label: model.name
}),
states: {
HOVER: () => ({
fillColor: '#38bdf866',
strokeColor: '#0ea5e9'
})
},
render: (opts) => new Style({
fill: new Fill({ color: opts.fillColor }),
stroke: new Stroke({ color: opts.strokeColor, width: 2 }),
text: new Text({
text: opts.label,
font: '600 13px Inter, sans-serif',
fill: new Fill({ color: '#1e293b' }),
stroke: new Stroke({ color: '#ffffff', width: 3 }),
overflow: true // Ensure label stays inside the polygon
})
})
}
4. Enabling Reshaping and Dragging
To make the polygons interactive, add the interactions configuration.
- Modify: Allows users to pull vertices or create new ones.
- Translate: Allows moving the entire polygon.
interactions: {
modify: {
enabled: true,
state: 'MODIFYING', // Optional: apply a style state during edit
},
translate: {
enabled: true,
cursor: 'move',
state: 'DRAGGING'
}
}
5. Integrating with the Component
Now, put it all together in your Angular component. Use the MapHostComponent and the onReady event to provide data to the layer API.
@Component({
selector: 'app-zone-map',
standalone: true,
imports: [MapHostComponent],
template: `<mff-map-host [config]="mapConfig" (ready)="onReady($event)"></mff-map-host>`
})
export class ZoneMapComponent {
readonly mapConfig: MapHostConfig = {
schema: {
layers: [zoneDescriptor]
},
osm: true
};
onReady(ctx: MapContext) {
const layerApi = ctx.layers['zones-layer'];
// Set initial data
layerApi.setModels([
{
id: 'zone-1',
name: 'Industrial Park',
color: '#f59e0b',
path: [[[27.5, 53.9], [27.6, 53.9], [27.6, 53.8], [27.5, 53.9]]]
}
]);
// Listen for changes (e.g., after user finishes dragging or modifying)
layerApi.onModelsChanged?.((changes) => {
changes.forEach(change => {
console.log(`Zone ${change.next.id} updated via ${change.reason}`);
// Here you can save change.next to your backend
});
});
}
}
Summary of Interaction Behavior
By using the declarative interactions block, the framework automatically manages:
- Hit-testing: Determining which polygon is under the cursor.
- Cursor Management: Changing the cursor to
moveorpointerautomatically. - State Synchronization: Applying the
DRAGGINGorHOVERstyles without manual logic. - Data Integrity: Ensuring that when a vertex is moved, your
AreaZonemodel is updated with the new coordinates viaapplyGeometryToModel.