The ability to deal with input events is an important part of your application. LuciadRIA provides built-in controls for the most common tasks, such as panning and zooming on a map. These controls are automatically enabled when you create a new Map.

By default, you get the following behavior on a Map:

Mouse Touch device Result API class

Drag the left or middle mouse button

Drag a single finger

Pans the map

PanController

Drag the right mouse button around the center of a 2D map

Two-finger twist gesture

Rotates the 2D map

RotateController

Drag the right mouse button left and right on a 3D map

Two-finger twist gesture

Rotates the 3D map

RotateController

Drag the right mouse button up and down on a 3D map

Two-finger drag up and down

Pitches the 3D map

RotateController

Scroll

Two-finger pinch gesture

Zooms the map

ZoomController

Scroll with SHIFT key held down

None

Zooms the map slowly

ZoomController

Click on selectable object

Tap on selectable object

Selects the object

SelectController

Click on selectable object, with SHIFT key held down

/

Adds or removes the object from the current selection

SelectController

Right-click on selectable object

"Long press" on selectable object

Opens a context menu

ContextMenuController

Move mouse over hoverable object

/

Make the object hovered

HoverController

Double-click with left mouse button

Double tap

Zooms in at the clicked / tapped location

ZoomController

Double-click with right mouse button

/

Zooms out from the clicked location

ZoomController

You can override this default behavior by implementing a custom Controller. In the LuciadRIA API, the Controller class deals with input events from the user. If an input event is not handled by a Controller, the default behavior is triggered. For example, if your Controller does not handle a click event, the default selection behavior is triggered.

You can activate and de-activate controllers by setting the Map.controller property. When you set this property, you de-activate any controller that was configured before, and activate the newly configured controller. The controllers get notified of this state change through the Controller.onDeactivate() and Controller.onActivate() methods.

In this tutorial, we describe some Controller implementations in the LuciadRIA API. You can also create your own custom controllers to handle custom user interaction. See Implementing custom user interaction for more information.

Creating new vector features using the CreateController

The CreateController class makes it possible to start an interactive session of vector feature creation.

By default, users can place feature vertices through mouse clicks or free-hand drawing. You can configure the drawing mode and the minimum and maximum number of points to determine the actions you expect from the user.

For your convenience, LuciadRIA provides a concrete implementation, BasicCreateController. This controller implements Createcontroller.onCreateNewObject() by creating a new shape based on a shape type token passed to its constructor. It also wraps the shape in a Feature instance.

This is an illustration of the usage of the BasicCreateController class:

Program: Using BasicCreateController (from samples/createedit/main.js)
const createController = new BasicCreateController(shapeType, {}, {
  finishOnSingleClick: true
});

See the sample CreateController in samples/common/ui/CreateShapeToolbar.tsx and samples/symbology/cop/ObjectWithUUIDCreateController.ts for complete illustrations.

Editing existing vector features

Graphical editing

To begin an editing session, use an EditController. During a graphical editing session, users can change the shape position by dragging the shape.

They can also change the shape geometry by dragging one of its vertices. Vertices automatically snap to other objects when they get close to them. To turn off snapping while editing, users can hold down the Ctrl-key.

They can also insert new points by activating one of the control points located between the vertices of a shape.

User can also delete vertices from the shape. Depending on the device they’re using, they can delete a point by pressing and holding a control point, or by pressing the Ctrl-key and clicking the control point. In a desktop environment, users can also right-click a control point and select Delete from the pop-up menu.

graphicalEdit
Figure 1. Graphically editing a line shape

When a user is editing an object, a commit is performed only at the end of an editing session. This prevents overly frequent updates of the Store during editing.

WebGL maps support editing in 2D and 3D.

Only 2D shapes or 2D aspects of 3D shapes are editable. For example, you can edit only the base shape of an extruded shape.

Textual editing of vector features via the context menu

You can execute custom actions from the context menu. This is the menu that pops up when a user right-clicks an object. Those actions are typically used for editing or changing textual properties of a domain object.

You can add actions to any FeatureLayer by implementing the onCreateContextMenu method.

See the Create and edit sample for example implementations of FeatureLayer.onCreateContextMenu.

Deleting vector features

You can delete vector features by removing them from their container model.

Customizing the default map behavior

By default, there is no Controller on the Map. In this case, you get the default map behavior. You can override the default behavior by setting a new controller on the map. If that controller consumes an event, and makes onGestureEvent return HandleEventResult.EVENT_HANDLED, the default behavior won’t be triggered.

For example, we can override the default panning behavior on the map by using a custom PanController.

Program: Using a custom PanController
class CustomPanController extends PanController {

  isInertia(gestureEvent: GestureEvent): boolean {
    return gestureEvent.inputType === "touch"; // only enable inertia for touch
  }

}

map.controller = new CustomPanController();

Because a PanController consumes pan events, through mouse or touch drag activity, the default behavior won’t be triggered.

If you want to prevent the default behavior completely, you can set a NoopController on the map. This prevents all default behavior.

Program: Preventing all default map behavior
map.controller = new NoopController(); // prevent _all_ default map behavior

Combining multiple controllers

You can combine multiple controllers with a CompositeController. This controller allows you to chain multiple controllers together, one after the other.

The CompositeController forwards onGestureEvent calls down the chain, until the first controller in the chain handles the event.

The order of the controllers in the chain is important. To avoid issues with onGestureEvent delegation, it’s recommended to chain only Controllers that handle distinct types of events. For example, avoid combining 2 controllers that both handle drag events.

You can use CompositeController and the controllers in @luciad/ria/view/controller to mimic, or completely customize, the default map behavior:

Program: Customizing the default map behavior
export function createDefaultController(): Controller {
  const defaultController = new CompositeController();
  defaultController.appendController(new SelectController());
  defaultController.appendController(new ContextMenuController());
  defaultController.appendController(new PanController());
  defaultController.appendController(new RotateController());
  defaultController.appendController(new ZoomController());
  defaultController.appendController(new HoverController());
  defaultController.appendController(new NoopController()); // completely prevent the default map behavior
  return defaultController;
}

// a PanController without inertia
class CustomPanController extends PanController {

  isInertia(gestureEvent: GestureEvent): boolean {
    return false;
  }

}

// a select controller that only allows selection by clicking on labels
class CustomSelectController extends SelectController {

  getPaintRepresentations(event: GestureEvent): PaintRepresentation[] {
    return [PaintRepresentation.LABEL];
  }

}

export function createCustomController(): Controller {
  const customController = new CompositeController();
  customController.appendController(new CustomSelectController()); // only allow selection by clicking on labels
  customController.appendController(new ContextMenuController()); // default context menu behavior
  customController.appendController(new CustomPanController()); // panning without inertia
  // customController.appendController(new RotateController()); // prevent the user from rotating the map
  customController.appendController(new ZoomController()); // default zooming behavior
  customController.appendController(new HoverController()); // default hovering behavior
  customController.appendController(new NoopController()); // stops the default map behavior from being triggered
  return customController;
}

If a CompositeController does not suffice, you can also implement your own Controller that delegates to specific Controller classes, using custom logic.