
This tutorial explains how you can create a handles provider.
The goal of this tutorial is to add support for editing a hippodrome with these handles:
-
A translate handle
-
A point handle to change the start point of the hippodrome
-
A point handle to change the end point of the hippodrome
-
A line handle to change the radius of the hippodrome
A hippodrome is represented by a CompositeRing
CompositeRing
CompositeRing
that is built up of 2 lines
lines
lines
and 2 circular arcs
circular arcs
circular arcs
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 hippodrome that we represent by a CompositeRing
CompositeRing
CompositeRing
geometry, we create a new geometry handles provider
geometry handles provider
geometry handles provider
implementation and implement these methods:
-
IGeometryHandlesProvider::canProvide
IGeometryHandlesProvider::canProvide
IGeometryHandlesProvider::canProvide
: to make sure that the new handle provider implementation is used during the editing of a hippodrome. -
IGeometryHandlesProvider::provideTranslateAction
IGeometryHandlesProvider::provideTranslateAction
IGeometryHandlesProvider::provideTranslateAction
: this method creates a translate action that afeature handles provider
feature handles provider
feature handles provider
can use. In this tutorial, we use theFeatureHandlesProvider
FeatureHandlesProvider
FeatureHandlesProvider
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 HippodromeHandlesProvider::canProvide(const std::shared_ptr<Observable<std::shared_ptr<Geometry>>>& geometry,
const std::shared_ptr<FeatureEditContext>& /*context*/) const {
const auto *hippoDrome = dynamic_cast<CompositeRing*>(geometry->getValue().get());
if (hippoDrome == nullptr || hippoDrome->getCurveCount() != 4) {
return false;
}
// A hippodrome is built up of a line, circular arc, a line and then a circular arc again. If the shape is not built in this order it is not a hippodrome.
const auto *line1 = dynamic_cast<Line*>(hippoDrome->getCurveAt(0).get());
const auto *curveArc1 = dynamic_cast<CircularArcByCenterPoint*>(hippoDrome->getCurveAt(1).get());
const auto *line2 = dynamic_cast<Line*>(hippoDrome->getCurveAt(2).get());
const auto *curveArc2 = dynamic_cast<CircularArcByCenterPoint*>(hippoDrome->getCurveAt(3).get());
return line1 && line2 && curveArc1 && curveArc2;
}
std::shared_ptr<ITranslateEditAction> HippodromeHandlesProvider::provideTranslateAction(
std::shared_ptr<Observable<std::shared_ptr<Geometry>>> geometry,
const std::shared_ptr<FeatureEditContext>& /*context*/,
std::shared_ptr<IGeometryEditCallback> geometryEditCallback) const {
auto observableHippodrome = deriveHippodrome(geometry);
return std::make_shared<HippodromeTranslateAction>(observableHippodrome, std::move(geometryEditCallback));
}
public bool CanProvide(Observable<Geometry> geometry, FeatureEditContext context)
{
CompositeRing hippodrome = geometry.Value as CompositeRing;
if (hippodrome == null || hippodrome.CurveCount != 4)
{
return false;
}
return geometry.Value is CompositeRing;
}
public ITranslateEditAction ProvideTranslateAction(Observable<Geometry> geometry, FeatureEditContext context, IGeometryEditCallback geometryEditCallback)
{
// Derive a composite ring. This method should only be called when canProvide is true, so we can assume that it is a Hippodrome
var observableHippodrome = Observable<CompositeRing>.Create(geometry.Value as CompositeRing);
// Create a translate action for the composite ring. This can be used by FeatureHandlesProvider (or a custom IFeatureHandlesProvider)
// to create a translate handle.
return new HippodromeTranslateAction(observableHippodrome, geometryEditCallback);
}
override fun canProvide(geometry: Observable<Geometry?>, context: FeatureEditContext): Boolean {
if (!(geometry.value!! is CompositeRing)) {
return false
}
val hippodrome = geometry.value!! as CompositeRing
if (hippodrome.curveCount.toInt() != 4) {
return false
}
return true
}
override fun provideTranslateAction(
geometry: Observable<Geometry?>,
context: FeatureEditContext,
geometryEditCallback: IGeometryEditCallback
): ITranslateEditAction {
val observableHippodrome = Observable(geometry!!.value as CompositeRing)
return HippodromeTranslateAction(observableHippodrome, geometryEditCallback)
}
We use this action:
class HippodromeTranslateAction final : public ITranslateEditAction {
public:
HippodromeTranslateAction(std::shared_ptr<Observable<std::shared_ptr<CompositeRing>>> hippodrome, std::shared_ptr<IGeometryEditCallback> callback)
: _hippodrome(std::move(hippodrome)), _callback(std::move(callback)) {
}
void translate(Coordinate translation, EventStatus translateStatus, ChangeStatus changeStatus) override {
if (translateStatus == EventStatus::Start) {
_initialHippodrome = _hippodrome->getValue();
}
auto newStartPosition = getStartPoint(_initialHippodrome) + translation;
auto newEndPosition = getEndPoint(_initialHippodrome) + translation;
auto newHippodrome = Hippodrome::create(newStartPosition, newEndPosition, getRadius(_initialHippodrome), _initialHippodrome->getReference());
_callback->onEdit(newHippodrome, changeStatus);
}
private:
std::shared_ptr<Observable<std::shared_ptr<CompositeRing>>> _hippodrome;
std::shared_ptr<IGeometryEditCallback> _callback;
std::shared_ptr<CompositeRing> _initialHippodrome;
};
private sealed class HippodromeTranslateAction : ITranslateEditAction
{
public HippodromeTranslateAction(Observable<CompositeRing> hippodrome, IGeometryEditCallback callback)
{
_hippodrome = hippodrome;
_callback = callback;
}
public void Translate(Coordinate translation, EventStatus translateStatus, ChangeStatus changeStatus)
{
if (translateStatus == EventStatus.Start)
{
_initialHippodrome = _hippodrome.Value;
}
var newStartPoint = Hippodrome.GetStartPoint(_initialHippodrome) + translation;
var newEndPoint = Hippodrome.GetEndPoint(_initialHippodrome) + translation;
var newHippodrome = Hippodrome.Create(newStartPoint, newEndPoint, Hippodrome.GetRadius(_initialHippodrome), _initialHippodrome.Reference);
_callback.OnEdit(newHippodrome, changeStatus);
}
private readonly Observable<CompositeRing> _hippodrome;
private readonly IGeometryEditCallback _callback;
private CompositeRing _initialHippodrome;
};
private class HippodromeTranslateAction(
private val hippodrome: Observable<CompositeRing?>,
private val callback: IGeometryEditCallback
) :
ITranslateEditAction {
private var initialHippodrome: CompositeRing? = null
override fun translate(
translation: Coordinate,
translateStatus: EventStatus,
changeStatus: ChangeStatus
) {
if (translateStatus == EventStatus.Start) {
initialHippodrome = hippodrome.value
}
val newStartPosition = Hippodrome.getStartPoint(initialHippodrome!!).add(translation)
val newEndPosition = Hippodrome.getEndPoint(initialHippodrome!!).add(translation)
val radius = Hippodrome.getRadius(initialHippodrome!!)
val newHippodrome = Hippodrome.create(
newStartPosition,
newEndPosition,
radius,
initialHippodrome!!.reference
)
callback.onEdit(newHippodrome, changeStatus)
}
}
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 2 point handles to edit the start and end points of the hippodrome, and a line handle to edit the radius of the hippodrome.
The edit state and the handles defined for a certain feature or geometry are managed in an
IEditHandles
IEditHandles
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 start point, end point and radius handle and make sure that the IEditHandles::getList
method returns them.
This sample code shows how you can do that:
_startPointHandle = createStartPointHandle(_hippodrome, callback);
static std::shared_ptr<PointEditHandle> createStartPointHandle(const std::shared_ptr<Observable<std::shared_ptr<CompositeRing>>>& observableHippodrome,
const std::shared_ptr<IGeometryEditCallback>& editCallback) {
auto hippodrome = observableHippodrome->getValue();
auto startPointProvider = Observable<std::shared_ptr<Point>>::create(GeometryFactory::createPoint(hippodrome->getReference(), getStartPoint(hippodrome)));
auto action = IPointEditAction::create([editCallback, observableHippodrome](const std::shared_ptr<Point>& location, ChangeStatus changeStatus) {
auto currentHippoDrome = observableHippodrome->getValue();
auto newHippodrome = Hippodrome::create(location->getLocation(), getEndPoint(currentHippoDrome), getRadius(currentHippoDrome),
currentHippoDrome->getReference());
editCallback->onEdit(newHippodrome, changeStatus);
});
auto handle = std::make_shared<PointEditHandle>(startPointProvider);
handle->addOnDragAction(action).cursor(MouseCursor::cross());
return handle;
}
_startPointHandle = CreateStartPointHandle(_hippodrome, geometryEditCallback);
private sealed class StartPointEditAction : IPointEditAction
{
private readonly Observable<Geometry> _hippodrome;
private readonly IGeometryEditCallback _editCallback;
public StartPointEditAction(Observable<Geometry> hippodrome, IGeometryEditCallback editCallback)
{
_hippodrome = hippodrome;
_editCallback = editCallback;
}
public void Execute(Point location, ChangeStatus changeStatus)
{
// Create a new hippodrome based on the new radius of the handle, and inform the IGeometryEditCallback
var currentHippodrome = _hippodrome.Value;
var calculations = GeodesyCalculations.Create(currentHippodrome.Reference, LineInterpolationType.Geodesic);
var endPoint = Hippodrome.GetEndPoint(currentHippodrome as CompositeRing);
var radius = Hippodrome.GetRadius(currentHippodrome as CompositeRing);
var newHippodrome = Hippodrome.Create(location.Location, endPoint, radius, currentHippodrome.Reference);
_editCallback.OnEdit(newHippodrome, changeStatus);
}
public void Execute(Point location, EventStatus eventStatus, ChangeStatus changeStatus)
{
Execute(location, changeStatus);
}
}
private static Point DeriveStartPoint(CompositeRing hippodrome)
{
return new Point(hippodrome.Reference, Hippodrome.GetStartPoint(hippodrome));
}
private static PointEditHandle CreateStartPointHandle(Observable<Geometry> hippodrome, IGeometryEditCallback editCallback)
{
var startPoint = Observable<Point>.Create(DeriveStartPoint(hippodrome.Value as CompositeRing));
var action = new StartPointEditAction(hippodrome, editCallback);
var handle = new PointEditHandle(startPoint);
handle.AddOnDragAction(action).Cursor(MouseCursor.Cross);
return handle;
}
startPointHandle = createStartPointHandle(hippodrome, geometryEditCallback)
private class StartPointEditAction(
private val hippodrome: Observable<Geometry?>,
private val editCallback: IGeometryEditCallback
) :
IPointEditAction {
override fun execute(
location: Point,
eventStatus: EventStatus,
changeStatus: ChangeStatus
) {
execute(location, changeStatus)
}
override fun execute(location: Point, changeStatus: ChangeStatus) {
// Create a new hippodrome with a new start point based on the handle, and inform the IGeometryEditCallback
val currentHippodrome = hippodrome.value as CompositeRing
val startPoint = location.location
val endPoint = Hippodrome.getEndPoint(currentHippodrome)
val radius = Hippodrome.getRadius(currentHippodrome)
val newHippodrome = Hippodrome.create(
startPoint, endPoint, radius, currentHippodrome.reference
)
editCallback.onEdit(newHippodrome, changeStatus)
}
}
private fun deriveStartPoint(hippodrome: CompositeRing): Point {
return Point(hippodrome.reference, Hippodrome.getStartPoint(hippodrome))
}
private fun createStartPointHandle(
hippodrome: Observable<Geometry?>,
editCallback: IGeometryEditCallback
): PointEditHandle {
val handleLocation =
Observable.create(deriveStartPoint(hippodrome.value!! as CompositeRing))
val action =
samples.editing.tutorial3.HippodromeHandlesProvider.HippodromeEditHandles.StartPointEditAction(
hippodrome,
editCallback
)
val handle = PointEditHandle(handleLocation)
handle.addOnDragAction(action).cursor(MouseCursor.Cross)
return handle
}
The end point is the same as the start point handle, the only difference being which point it uses.
_radiusHandle = createRadiusHandle(_hippodrome, callback);
static std::shared_ptr<LineEditHandle> createRadiusHandle(const std::shared_ptr<Observable<std::shared_ptr<CompositeRing>>>& observableHippodrome,
const std::shared_ptr<IGeometryEditCallback>& editCallback) {
auto radiusProvider = Observable<std::shared_ptr<Geometry>>::create(observableHippodrome->getValue());
auto action = IPointEditAction::create([editCallback, observableHippodrome](const std::shared_ptr<Point>& location, ChangeStatus changeStatus) {
auto currentHippodrome = observableHippodrome->getValue();
auto calculations = GeodesyCalculations::create(currentHippodrome->getReference(), LineInterpolationType::Geodesic);
auto newRadius = *calculations->distance2D(location->getLocation(), closestPointOnSegment(location->getLocation(), getStartPoint(currentHippodrome),
getEndPoint(currentHippodrome)));
auto newHippodrome = Hippodrome::create(getStartPoint(currentHippodrome), getEndPoint(currentHippodrome), newRadius, currentHippodrome->getReference());
editCallback->onEdit(newHippodrome, changeStatus);
});
auto handle = std::make_shared<LineEditHandle>(radiusProvider);
handle->addOnDragAction(action).cursor(MouseCursor::sizeHorizontal());
return handle;
}
_radiusHandle = CreateRadiusHandle(_hippodrome, geometryEditCallback);
private sealed class RadiusPointEditAction : IPointEditAction
{
private readonly Observable<Geometry> _hippodrome;
private readonly IGeometryEditCallback _editCallback;
public RadiusPointEditAction(Observable<Geometry> hippodrome, IGeometryEditCallback editCallback)
{
_hippodrome = hippodrome;
_editCallback = editCallback;
}
public void Execute(Point location, EventStatus eventStatus, ChangeStatus changeStatus)
{
Execute(location, changeStatus);
}
public void Execute(Point location, ChangeStatus changeStatus)
{
// Create a new hippodrome based on the new radius of the handle, and inform the IGeometryEditCallback
var currentHippodrome = _hippodrome.Value;
var calculations = GeodesyCalculations.Create(currentHippodrome.Reference, LineInterpolationType.Geodesic);
var startPoint = Hippodrome.GetStartPoint(currentHippodrome as CompositeRing);
var endPoint = Hippodrome.GetEndPoint(currentHippodrome as CompositeRing);
var newRadius = calculations.Distance2D(Hippodrome.ClosestPointOnSegment(location.Location, startPoint, endPoint), location.Location).Value;
var newHippodrome = Hippodrome.Create(startPoint, endPoint, newRadius, currentHippodrome.Reference);
_editCallback.OnEdit(newHippodrome, changeStatus);
}
}
private static LineEditHandle CreateRadiusHandle(Observable<Geometry> hippodrome, IGeometryEditCallback editCallback)
{
var outline = Observable<Geometry>.Create(hippodrome.Value as Geometry);
var action = new RadiusPointEditAction(hippodrome, editCallback);
var handle = new LineEditHandle(outline);
handle.AddOnDragAction(action).Cursor(MouseCursor.Cross);
return handle;
}
radiusHandle = createRadiusHandle(hippodrome, geometryEditCallback)
private class RadiusPointEditAction(
private val hippodrome: Observable<Geometry?>,
private val editCallback: IGeometryEditCallback
) :
IPointEditAction {
override fun execute(
location: Point,
eventStatus: EventStatus,
changeStatus: ChangeStatus
) {
execute(location, changeStatus)
}
override fun execute(location: Point, changeStatus: ChangeStatus) {
// Create a new hippodrome based on the new radius of the handle, and inform the IGeometryEditCallback
val currentHippodrome = hippodrome.value as CompositeRing
val calculations = GeodesyCalculations.create(
currentHippodrome.reference,
LineInterpolationType.Geodesic
)
val startPoint = Hippodrome.getStartPoint(currentHippodrome)
val endPoint = Hippodrome.getEndPoint(currentHippodrome)
val newRadius = calculations.distance2D(
Hippodrome.ClosestPointOnSegment(
location.location,
startPoint,
endPoint
), location.location
)!!
val newHippodrome = Hippodrome.create(
startPoint, endPoint, newRadius, currentHippodrome.reference
)
editCallback.onEdit(newHippodrome, changeStatus)
}
}
private fun createRadiusHandle(
hippodrome: Observable<Geometry?>,
editCallback: IGeometryEditCallback
): LineEditHandle {
val action =
samples.editing.tutorial3.HippodromeHandlesProvider.HippodromeEditHandles.RadiusPointEditAction(
hippodrome,
editCallback
)
val handle = LineEditHandle(hippodrome)
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 hippodrome, so we’re using an Observable
Observable
Observable
for a CompositeRing
CompositeRing
CompositeRing
.
This code shows how to detect changes:
HippodromeEditHandles(std::shared_ptr<Observable<std::shared_ptr<CompositeRing>>> hippodrome, const std::shared_ptr<IGeometryEditCallback>& callback)
: _hippodrome{std::move(hippodrome)} {
_hippodromeCallback = IInvalidationCallback::create([&]() {
onGeometryChanged();
auto hippodromeValue = _hippodrome->getValue();
_startPointHandle->getLocationProvider()->setValue(GeometryFactory::createPoint(hippodromeValue->getReference(), getStartPoint(hippodromeValue)));
_endPointHandle->getLocationProvider()->setValue(GeometryFactory::createPoint(hippodromeValue->getReference(), getEndPoint(hippodromeValue)));
_radiusHandle->getGeometryProvider()->setValue(hippodromeValue);
});
_hippodrome->addCallback(_hippodromeCallback);
}
public HippodromeEditHandles(Observable<Geometry> hippodrome, IGeometryEditCallback geometryEditCallback)
{
_hippodrome = hippodrome;
IInvalidationCallback hippodromeCallback = new HippodromeInvalidateCallback(hippodrome, this,
_radiusHandle.GeometryProvider, _startPointHandle.LocationProvider, _endPointHandle.LocationProvider);
_hippodrome.AddCallback(hippodromeCallback);
}
private sealed class HippodromeInvalidateCallback : IInvalidationCallback
{
private readonly Observable<Geometry> _hippodrome;
private readonly WeakReference<HippodromeEditHandles> _editHandles;
private readonly Observable<Geometry> _radiusHandleGeometry;
private readonly Observable<Point> _startPointHandle;
private readonly Observable<Point> _endPointHandle;
public HippodromeInvalidateCallback(Observable<Geometry> hippodrome, HippodromeEditHandles editHandles, Observable<Geometry> radiusHandleGeometry, Observable<Point> startPointHandle, Observable<Point> endPointHandle)
{
_hippodrome = hippodrome;
_editHandles = new WeakReference<HippodromeEditHandles>(editHandles);
_radiusHandleGeometry = radiusHandleGeometry;
_startPointHandle = startPointHandle;
_endPointHandle = endPointHandle;
}
public void OnInvalidate()
{
if (!_editHandles.TryGetTarget(out var editHandles))
{
_hippodrome.RemoveCallback(this);
return;
}
// Handle all possible changes to the geometry
editHandles.OnGeometryChanged();
_radiusHandleGeometry.Value = _hippodrome.Value;
_startPointHandle.Value = DeriveStartPoint(_hippodrome.Value as CompositeRing);
_endPointHandle.Value = DeriveEndPoint(_hippodrome.Value as CompositeRing);
}
}
init {
val hippodromeCallback: IInvalidationCallback =
HippodromeInvalidateCallback(
hippodrome,
this,
startPointHandle.locationProvider,
endPointHandle.locationProvider
)
hippodrome.addCallback(hippodromeCallback)
}
private class HippodromeInvalidateCallback(
private val hippodrome: Observable<Geometry?>,
editHandles: HippodromeEditHandles,
private val startPointHandle: Observable<Point?>,
private val endPointHandle: Observable<Point?>,
) :
IInvalidationCallback {
private val editHandles: WeakReference<HippodromeEditHandles>
init {
this.editHandles = WeakReference(editHandles)
}
override fun onInvalidate() {
val editHandles = editHandles.get()
if (editHandles == null) {
hippodrome.removeCallback(this)
return
}
// Handle all possible changes to the geometry
editHandles.onGeometryChanged()
startPointHandle.value = deriveStartPoint(hippodrome.value as CompositeRing)
endPointHandle.value = deriveEndPoint(hippodrome.value as CompositeRing)
}
}
In this tutorial, we only need an action to check if the edited geometry is still a
CompositeRing
CompositeRing
CompositeRing
. 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
IEditHandles
IEditHandles
implementation notifies the registered observers
observers
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
Feature
Feature
doesn’t contain a CompositeRing
CompositeRing
CompositeRing
anymore, but that another geometry type is required.
A CompositeRing
CompositeRing
CompositeRing
implementation can send a notification that it isn’t suitable anymore for the current geometry.
This sample code demonstrates such a notification:
void fireInvalidHandlesEvent() {
auto observers = _observers;
for (const auto& observer : observers) {
observer->onEditHandlesInvalid();
}
}
private void OnGeometryChanged()
{
if (_hippodrome.Value == null)
{
// This set of handles should not be used anymore since the current geometry is not a Hippodrome anymore.
FireInvalidHandlesEvent();
}
}
private void FireInvalidHandlesEvent()
{
var observers = new List<IEditHandlesObserver>(_observers);
foreach (var observer in observers)
{
observer.OnEditHandlesInvalid();
}
}
private fun onGeometryChanged() {
if (hippodrome.value == null) {
// This set of handles should not be used anymore since the current geometry is not a Hippodrome anymore.
fireInvalidHandlesEvent()
}
}
private fun fireInvalidHandlesEvent() {
val observers: List<IEditHandlesObserver> = ArrayList(
observers
)
for (observer in 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> HippodromeHandlesProvider::provide(
std::shared_ptr<Observable<std::shared_ptr<Geometry>>> geometry,
const std::shared_ptr<FeatureEditContext>& /*context*/,
std::shared_ptr<IGeometryEditCallback> geometryEditCallback) const {
auto observableHippodrome = deriveHippodrome(geometry);
return std::make_shared<HippodromeEditHandles>(observableHippodrome, geometryEditCallback);
}
public IEditHandles Provide(Observable<Geometry> geometry, FeatureEditContext context, IGeometryEditCallback geometryEditCallback)
{
// Create a set of handles for this hippodrome
return new HippodromeEditHandles(geometry, geometryEditCallback);
}
override fun provide(
geometry: Observable<Geometry?>,
context: FeatureEditContext,
geometryEditCallback: IGeometryEditCallback
): IEditHandles {
return HippodromeEditHandles(geometry, 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
IFeatureEditConfiguration
IFeatureEditConfiguration
, and set that configuration
on the FeatureLayer
FeatureLayer
FeatureLayer
.
class CustomEditConfiguration final : public IFeatureEditConfiguration {
public:
CustomEditConfiguration() {
// Create an IGeometryHandlesFactory that can also edit a hippodrome using 2 point handles and a line handle
auto hippodromeHandlesProvider = std::make_shared<HippodromeHandlesProvider>();
auto compositeHandlesProvider = CompositeGeometryHandlesProvider::createDefault();
compositeHandlesProvider->add(hippodromeHandlesProvider, Priority::high());
// Make sure the FeatureHandlesProvider uses this custom IGeometryHandlesFactory
_featureHandlesProvider = std::make_shared<FeatureHandlesProvider>();
_featureHandlesProvider->setGeometryHandlesProvider(compositeHandlesProvider);
}
void edit(const Feature& /*feature*/, LayerId /*layerId*/, const std::shared_ptr<Map>& /*map*/, FeatureEditConfigurationBuilder& builder) const override {
// Use a custom handles provider for all features
builder.handlesProvider(_featureHandlesProvider).submit();
}
private:
std::shared_ptr<FeatureHandlesProvider> _featureHandlesProvider;
};
// 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();
private sealed class CustomEditConfiguration : IFeatureEditConfiguration
{
private readonly FeatureHandlesProvider _featureHandlesProvider;
public void Edit(Feature feature, ulong layerId, Map map, FeatureEditConfigurationBuilder builder)
{
// Use a custom handles provider for all features
builder.HandlesProvider(_featureHandlesProvider).Submit();
}
}
// Use a custom edit configuration that changes the editing behavior for this layer
var customEditConfiguration = new CustomEditConfiguration();
// Register the configuration. This makes the layer editable by default
return FeatureLayer.NewBuilder()
.Model(model)
.Title("Tutorial 3")
.EditConfiguration(customEditConfiguration)
.Build();
private class CustomEditConfiguration : IFeatureEditConfiguration {
private val featureHandlesProvider: FeatureHandlesProvider
init {
// Create an IGeometryHandlesFactory that can also edit a hippodrome using 2 point handles and a line handle
val compositeHandlesProvider = CompositeGeometryHandlesProvider.createDefault()
compositeHandlesProvider.add(HippodromeHandlesProvider, Priority.High)
// Make sure the FeatureHandlesProvider uses this custom IGeometryHandlesFactory
featureHandlesProvider = FeatureHandlesProvider()
featureHandlesProvider.geometryHandlesProvider = compositeHandlesProvider
}
override fun edit(
feature: Feature,
layerId: Long,
map: Map,
builder: FeatureEditConfigurationBuilder
) {
// Use a custom handles provider for all features
builder.handlesProvider(featureHandlesProvider).submit()
}
}
// Use a custom edit configuration that changes the editing behavior for this layer
val customEditConfiguration = CustomEditConfiguration()
// Register the configuration. This makes the layer editable by default
return FeatureLayer.newBuilder()
.model(model)
.title("Tutorial 3")
.editConfiguration(customEditConfiguration)
.build()
private fun createFeatureModel(): IFeatureModel {
val reference = CoordinateReferenceProvider.create("EPSG:4326")
// Add a predefined feature to demonstrate the custom editing capabilities
val hippodrome = Hippodrome.create(
Coordinate(30.0, -7.0),
Coordinate(50.0, -7.0),
500000.0,
reference
)
val feature = createFeature(hippodrome, 0)
// Create an editable model
return createModel(reference, listOf(feature))
}