This tutorial explains how you can create a handles provider.

The goal of this tutorial is to add support for editing a CircleByCenterPoint with these handles:

  • A translate handle

  • A point handle to change the radius of the circle

Other customizations

You need to customize a handles provider for advanced use cases only. For other customization methods, see this article.

The code snippets in this tutorial are available in the editing sample.

See this article for more information about editing.

Step 1 - Add support for translation

To add support for the CircleByCenterPoint geometry, we create a new geometry handles provider implementation. To add support for translation, we must implement these methods:

This sample code shows how you can do that:

Program: Add support for translation
bool CircleBy2PointsHandlesProvider::canProvide(const std::shared_ptr<Observable<std::shared_ptr<Geometry>>>& geometry,
const std::shared_ptr<FeatureEditContext>& /*context*/) const {
return dynamic_cast<CircleByCenterPoint*>(geometry->getValue().get()) != nullptr;
}
std::shared_ptr<ITranslateEditAction> CircleBy2PointsHandlesProvider::provideTranslateAction(
std::shared_ptr<Observable<std::shared_ptr<Geometry>>> geometry,
const std::shared_ptr<FeatureEditContext>& /*context*/,
std::shared_ptr<IGeometryEditCallback> geometryEditCallback) const {
// Derive a circle. This method should only be called when canProvide is true, so we can assume that it is a CircleByCenterPoint
auto observableCircle = ObservableGeometryUtil::deriveCircleByCenterPoint(geometry);
// Create a translate action for the circle. This can be used by FeatureHandlesProvider (or a custom IFeatureHandlesProvider)
// to create a translate handle.
return std::make_shared<CircleTranslateAction>(observableCircle, std::move(geometryEditCallback));
}

We use this action:

Program: ITranslateEditAction implementation
class CircleTranslateAction final : public ITranslateEditAction {
public:
CircleTranslateAction(std::shared_ptr<Observable<std::shared_ptr<CircleByCenterPoint>>> circle, std::shared_ptr<IGeometryEditCallback> callback)
: _circle{std::move(circle)}, _callback(std::move(callback)) {
}
void translate(Coordinate translation, EventStatus translateStatus, ChangeStatus changeStatus) override {
if (translateStatus == EventStatus::Start) {
_initialCircle = _circle->getValue();
}
auto newCenter = _initialCircle->getCenter() + translation;
auto newCircle = GeometryFactory::createCircleByCenterPoint(_initialCircle->getReference(), newCenter, _initialCircle->getRadius());
_callback->onEdit(newCircle, changeStatus);
}
private:
std::shared_ptr<Observable<std::shared_ptr<CircleByCenterPoint>>> _circle;
std::shared_ptr<IGeometryEditCallback> _callback;
std::shared_ptr<CircleByCenterPoint> _initialCircle;
};

Step 2 - Define the handles

The next step is to define which handles to use for editing certain aspects of the geometry. In this tutorial, we define a single point handle that users can drag to change the radius of the circle.

The edit state and the handles defined for a certain feature or geometry are managed in an IEditHandles implementation.

  • Provide a set of handles for the geometry

  • Adapt the handles when the geometry changes

  • Send out notifications when the handles set becomes invalid

a) Provide a set of handles for the geometry

In this tutorial, we create a new radius handle and make sure that the IEditHandles::getList method returns it. This sample code shows how you can do that:

Program: Add radius handle
_radiusHandle = createRadiusHandle(_circle, geometryEditCallback);
std::vector<std::shared_ptr<IEditHandle>> getList() const override {
return {_radiusHandle};
}
static std::shared_ptr<PointEditHandle> createRadiusHandle(const std::shared_ptr<Observable<std::shared_ptr<CircleByCenterPoint>>>& circle,
const std::shared_ptr<IGeometryEditCallback>& editCallback) {
auto handleLocation = ObservableCircleByCenterPointUtil::derivePointAtAngle(circle, 0);
auto action = IPointEditAction::create([editCallback, circle](const std::shared_ptr<Point>& location, ChangeStatus changeStatus) {
// Create a new circle based on the new radius of the handle, and inform the IGeometryEditCallback
auto currentCircle = circle->getValue();
auto calculations = GeodesyCalculations::create(currentCircle->getReference(), LineInterpolationType::Geodesic);
auto newRadius = *calculations->distance2D(currentCircle->getCenter(), location->getLocation());
auto newCircle = GeometryFactory::createCircleByCenterPoint(currentCircle->getReference(), currentCircle->getCenter(), newRadius);
editCallback->onEdit(newCircle, changeStatus);
});
auto handle = std::make_shared<PointEditHandle>(handleLocation);
handle->addOnDragAction(action).cursor(MouseCursor::cross());
return handle;
}

b) Adapt the handles when the geometry changes

To adapt the handles to a geometry change, add an observer to the observable geometry or feature used during editing. In this tutorial, we edit a CircleByCenterPoint, so we’re using an Observable for a CircleByCenterPoint.

This code shows how to detect changes:

Program: Detect changes to the edited CircleByCenterPoint
CircleBy2PointsEditHandles(std::shared_ptr<Observable<std::shared_ptr<CircleByCenterPoint>>> circle,
const std::shared_ptr<IGeometryEditCallback>& geometryEditCallback)
: _circle(std::move(circle)) {
_circleCallback = IInvalidationCallback::create([&]() {
// Handle all possible changes to the geometry
onGeometryChanged();
});
_circle->addCallback(_circleCallback);
}
~CircleBy2PointsEditHandles() override {
_circle->removeCallback(_circleCallback);
}

In this tutorial, we only need an action to check if the edited geometry is still a CircleByCenterPoint. See c) Send out notifications when the set of handles becomes invalid. Apart from that, we don’t need any other actions to make sure that the handles are still up-to-date.

For other geometry types, you may need to do more, though. For example, when the point count of a polyline changes, you must add or remove handles. In that case, you must make sure that your IEditHandles implementation notifies the registered observers of these additions and removals.

c) Send out notifications when the set of handles becomes invalid

An edited geometry can change from one geometry type to another for some reason. For example, the model can decide that a Feature doesn’t contain a CircleByCenterPoint anymore, but that another geometry type is required.

An IEditHandles implementation can send a notification that it isn’t suitable anymore for the current geometry. This sample code demonstrates such a notification:

Program: Notify that a set of handles isn’t valid anymore
void onGeometryChanged() {
if (!_circle->getValue()) {
// This set of handles should not be used anymore since the current geometry is not a CircleByCenterPoint anymore.
fireInvalidHandlesEvent();
}
}
void fireInvalidHandlesEvent() {
auto observers = _observers;
for (const auto& observer : observers) {
observer->onEditHandlesInvalid();
}
}

d) Use the IEditHandles implementation

We must now make sure that our custom handles provider uses the handles we defined:

Program: Add support for the new handles
std::shared_ptr<IEditHandles> CircleBy2PointsHandlesProvider::provide(std::shared_ptr<Observable<std::shared_ptr<Geometry>>> geometry,
const std::shared_ptr<FeatureEditContext>& /*context*/,
std::shared_ptr<IGeometryEditCallback> geometryEditCallback) const {
// Derive a circle. This method should only be called when canProvide is true, so we can assume that it is a CircleByCenterPoint
auto observableCircle = ObservableGeometryUtil::deriveCircleByCenterPoint(geometry);
// Create a set of handles for this circle
return std::make_shared<CircleBy2PointsEditHandles>(observableCircle, geometryEditCallback);
}

Step 3 - Make sure that the new handles provider is used

We must make sure that the editing framework uses our custom handles provider, so we configure it in an IFeatureEditConfiguration, and set that configuration on the FeatureLayer.

Program: Creating an IFeatureHandlesProvider
// Use a custom edit configuration that changes the editing behavior for this layer
auto customEditConfiguration = std::make_shared<CustomEditConfiguration>();
// Register the configuration. This makes the layer editable by default
return FeatureLayer::newBuilder()
.model(model) //
.title("Tutorial 3")
.editConfiguration(customEditConfiguration)
.build();