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:
-
IGeometryHandlesProvider::canProvide
: to make sure that the new handle provider implementation is used during the editing of aCircleByCenterPoint
. -
IGeometryHandlesProvider::provideTranslateAction
: this method creates a translate action that afeature handles provider
can use. In this tutorial, we use theFeatureHandlesProvider
implementation. This implementation calls theprovideTranslateAction
method, and uses the returned action to create a handle that can translate the feature.
This sample code shows how you can do that:
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:
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.
IEditHandles
must:
-
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:
_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:
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:
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:
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
.
// 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();