Dragging and Modifying Features
Interacting with Features: Dragging and Modifying
One of the most powerful aspects of aur-openlayers is its ability to synchronize map interactions—like dragging a point or reshaping a polygon—directly with your Angular business models. Instead of writing complex event listeners, you define interactions declaratively within your layer descriptor.
This guide walks you through enabling dragging (Translate) and reshaping (Modify) for your features.
Step 1: Prepare the Model for Two-Way Synchronization
For a feature to be draggable, the framework needs to know how to translate a map movement back into your data model. This is handled by the applyGeometryToModel function in the geometry section of your FeatureDescriptor.
// Example: A simple Point model synchronization
{
id: (model: MapPoint) => model.id,
geometry: {
fromModel: (model: MapPoint) => new Point(fromLonLat([model.lng, model.lat])),
// This is called when the user finishes dragging
applyGeometryToModel: (prev: MapPoint, geom: Geometry) => {
if (!(geom instanceof Point)) return prev;
const [lng, lat] = toLonLat(geom.getCoordinates());
// Return a new immutable instance of your model
return { ...prev, lat, lng };
},
}
}
Step 2: Enable the Translate (Drag) Interaction
To make a feature draggable, add a translate configuration to the interactions property. This automatically manages the OpenLayers Translate interaction under the hood.
{
id: 'points-layer',
feature: {
// ... id and geometry logic
interactions: {
translate: {
enabled: true, // Can be a boolean or a function: () => this.isEditMode
cursor: 'grabbing', // The CSS cursor to show when hovering/dragging
state: 'DRAG', // Applies the 'DRAG' style state during the interaction
onStart: () => {
console.log('Started dragging');
return true; // Return true to allow the interaction
},
onEnd: (item) => {
console.log('Finished dragging:', item.model);
}
}
}
}
}
Step 3: Use States for Visual Feedback
Users need visual confirmation that an object is being moved. You can define specific styles for the DRAG state (or any custom state name you provided in the interaction config).
style: {
base: (model) => ({ color: '#2563eb', radius: 7 }),
states: {
DRAG: () => ({
color: '#dc2626', // Change color to red while dragging
radius: 10, // Make it slightly larger
}),
},
render: (opts) => new Style({
image: new CircleStyle({
radius: opts.radius,
fill: new Fill({ color: opts.color }),
}),
})
}
Step 4: Constrain Dragging with pickTarget
Often, you don't want every feature on a layer to be draggable at the same time. You can use pickTarget to implement logic like "only allow dragging the currently selected item."
In this example, we only allow dragging if the feature matches an editingPointId stored in our component:
interactions: {
translate: {
pickTarget: ({ candidates }) => {
// 'candidates' contains the features found under the pointer
return candidates.find(c => c.model.id === this.editingPointId);
},
onStart: () => {
// Use NgZone.run if you need to trigger Angular change detection
this.zone.run(() => this.isDragging = true);
return true;
}
}
}
Step 5: Modify Complex Geometries
While translate moves an entire feature, modify allows users to edit the vertices of lines or polygons. The setup is nearly identical to translate but is used for LineString or Polygon geometries.
interactions: {
modify: {
enabled: () => this.isEditMode,
state: 'MODIFYING',
onEnd: (item) => {
// The framework calls applyGeometryToModel automatically
// after the modification is complete.
this.saveChanges(item.model);
}
}
}
Handling Changes in the Component
When a user finishes a drag or modify interaction, the layer's onModelsChanged observable will emit the change. You should use this hook to update your application state or send data to a backend.
// Inside your component's onReady(ctx: MapContext)
const layerApi = ctx.layers['points-layer'];
layerApi.onModelsChanged?.((changes) => {
changes.forEach(change => {
if (change.reason === 'translate') {
console.log(`Model ${change.next.id} moved to`, change.next.lat, change.next.lng);
this.myApiService.updatePosition(change.next);
}
});
});
Summary Checklist
- Geometry Mapping: Ensure
applyGeometryToModelis implemented to handle incoming map coordinates. - Interaction Config: Add
translateormodifyto the layer descriptor. - UX: Define a
state(e.g.,'DRAG') and provide a corresponding style in thestatesblock. - Filtering: Use
pickTargetif you need to limit which specific features can be interacted with.