This tutorial explains how you can add more handles to an existing handles provider.
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 an extra handle to an existing IEditHandles
The best way to add an extra handle to a set of handles is to wrap an existing IEditHandles
IEditHandles
IEditHandles
instance, and add the
handle. In this tutorial, we use two classes for this:
-
EditHandlesWrapper
: thisIEditHandles
IEditHandles
IEditHandles
implementation is a utility class that makes it easier to wrap otherIEditHandles
IEditHandles
IEditHandles
instances. You can use it to override theIEditHandles::getList
IEditHandles::getList
IEditHandles::getList
method to add more handles. Any change in the delegateIEditHandles
IEditHandles
IEditHandles
also causes the wrapper to call theconfigured
configured
configured
observers. -
CustomFeatureEditHandles
: this class extends fromEditHandlesWrapper
, and adds a handle that lets you change a 'transparency' property of the edited Feature.
The following code shows how you can do this. You can find the full code in the sample.
class CustomFeatureEditHandles final : public EditHandlesWrapper {
public:
explicit CustomFeatureEditHandles(std::shared_ptr<Observable<Feature>> feature,
std::shared_ptr<IFeatureEditCallback> featureEditCallback,
std::shared_ptr<IEditHandles> delegateHandles,
const std::shared_ptr<FeatureEditContext>& context)
: EditHandlesWrapper(std::move(delegateHandles)), _feature{std::move(feature)}, _featureEditCallback{std::move(featureEditCallback)} {
// Create a handle that controls the transparency
_transparencyHandle = createTransparencyHandle(context);
}
std::vector<std::shared_ptr<IEditHandle>> getList() const override {
auto handles = EditHandlesWrapper::getList();
if (_transparencyHandle) {
handles.emplace_back(_transparencyHandle);
}
return handles;
}
private:
std::shared_ptr<PointEditHandle> createTransparencyHandle(const std::shared_ptr<FeatureEditContext>& context) {
// Create a handle that changes the transparency when the handle is dragged
return handle;
}
};
internal sealed class CustomFeatureEditHandles : EditHandlesWrapper
{
private readonly Observable<Feature> _feature; // The current feature
private readonly IFeatureEditCallback _featureEditCallback; // Called by the transparency handle when changing the feature state
private Observable<Polyline> _polyline; // The current polyline. This is derived from _feature
private IInvalidationCallback _featureInvalidateCallback; // Used to detect feature changes, so that the handle location can be updated
private readonly PointEditHandle _transparencyHandle; // The handle used to edit the transparency
public CustomFeatureEditHandles(Observable<Feature> feature, IFeatureEditCallback featureEditCallback, IEditHandles delegateHandles, FeatureEditContext context) :
base(delegateHandles)
{
_feature = feature;
_featureEditCallback = featureEditCallback;
// Create a handle that controls the transparency
_transparencyHandle = CreateTransparencyHandle(context);
}
public override IList<IEditHandle> GetList()
{
var handles = base.GetList();
if (_transparencyHandle == null) return handles;
handles = new List<IEditHandle>(handles) { _transparencyHandle };
return handles;
}
private PointEditHandle CreateTransparencyHandle(FeatureEditContext context)
{
// Create a handle that changes the transparency when the handle is dragged
return handle;
}
}
private class CustomFeatureEditHandles(// The current feature
private val feature: Observable<Feature>, // Called by the transparency handle when changing the feature state
private val featureEditCallback: IFeatureEditCallback,
delegateHandles: IEditHandles,
context: FeatureEditContext
) :
EditHandlesWrapper(delegateHandles) {
private lateinit var polyline // The current polyline. This is derived from _feature
: Observable<Polyline?>
private val transparencyHandle // The handle used to edit the transparency
: PointEditHandle
init {
// Create a handle that controls the transparency
transparencyHandle = createTransparencyHandle(context)
}
override fun getList(): List<IEditHandle> {
return super.getList().toMutableList().apply {
add(transparencyHandle)
}
}
private fun createTransparencyHandle(context: FeatureEditContext): PointEditHandle {
// Create a handle that changes the transparency when the handle is dragged
return handle
}
companion object {
private fun deriveLocationFromTransparency(
polyline: Polyline?,
feature: Feature
): Point? {
return polyline?.let {
val transparency = feature.getValue<Double>(EditingModelFactory.transparencyPropertyPath)!!.apply { coerceIn(0.1, 1.0) }
val location0 = polyline.getPoint(0)
val location1 = polyline.getPoint(1)
val fraction = transparency * 0.25
val geodesy = GeodesyCalculations.create(it.reference, it.interpolationType)
val newLocation = geodesy.interpolate(location0, location1, -fraction)
return Point(it.reference, newLocation!!)
}
}
fun dot(v1: Coordinate, v2: Coordinate): Double {
return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z;
}
fun parametricDistanceOfClosestPointOnLine(
pt: Coordinate,
p0: Coordinate,
p1: Coordinate
): Double {
val dt0 = Coordinate(pt.x - p0.x, pt.y - p0.y, pt.z - p0.z)
val d10 = Coordinate(p1.x - p0.x, p1.y - p0.y, p1.z - p0.z)
val denominator = dot(d10, d10)
return if (denominator != 0.0) {
dot(dt0, d10) / denominator
} else 0.0
}
fun deriveTransparencyFromLocation(
handleLocation: Point,
polyline: Polyline
): Double {
val location0 = polyline.getPoint(0)
val location1 = polyline.getPoint(1)
var closestPointParam = parametricDistanceOfClosestPointOnLine(handleLocation.location, location0, location1);
closestPointParam = Math.min(closestPointParam, 0.0);
val closestPoint = Coordinate(
location0.x + (location1.x - location0.x) * closestPointParam,
location0.y + (location1.y - location0.y) * closestPointParam,
location0.z + (location1.z - location0.z) * closestPointParam
)
val geodesy = GeodesyCalculations.create(polyline.reference, polyline.interpolationType)
val distance01 = geodesy.distance2D(location0, location1)
val distanceTransparency = geodesy.distance2D(location0, closestPoint)
if (distance01 == null || distanceTransparency == null) {
return 1.0
}
val transparency = distanceTransparency / (distance01 * 0.25)
return transparency.coerceIn(0.1, 1.0)
}
}
}
Step 2 - Create a handles provider
To make sure that our custom IEditHandles
IEditHandles
IEditHandles
is used, we must provide it through an
IFeatureHandlesProvider
IFeatureHandlesProvider
IFeatureHandlesProvider
implementation. In this tutorial,
we introduce the CustomFeatureHandlesProvider
class for this purpose.
This class mainly delegates to an existing IFeatureHandlesProvider
IFeatureHandlesProvider
IFeatureHandlesProvider
, and also creates a new CustomFeatureEditHandles
instance when we call it.
CustomFeatureHandlesProvider::CustomFeatureHandlesProvider() : _delegateHandlesProvider(std::make_shared<FeatureHandlesProvider>()) {
}
bool CustomFeatureHandlesProvider::canProvide(const std::shared_ptr<Observable<Feature>>& feature, const std::shared_ptr<FeatureEditContext>& context) const {
return _delegateHandlesProvider->canProvide(feature, context);
}
std::shared_ptr<IEditHandles> CustomFeatureHandlesProvider::provide(std::shared_ptr<Observable<Feature>> feature,
const std::shared_ptr<FeatureEditContext>& context,
std::shared_ptr<IFeatureEditCallback> featureEditCallback) const {
auto delegateHandles = _delegateHandlesProvider->provide(feature, context, featureEditCallback);
return std::make_shared<CustomFeatureEditHandles>(std::move(feature), std::move(featureEditCallback), delegateHandles, context);
}
public sealed class CustomFeatureHandlesProvider : IFeatureHandlesProvider
{
private readonly IFeatureHandlesProvider _delegateHandlesProvider;
public CustomFeatureHandlesProvider()
{
_delegateHandlesProvider = new FeatureHandlesProvider();
}
public bool CanProvide(Observable<Feature> feature, FeatureEditContext context)
{
return _delegateHandlesProvider.CanProvide(feature, context);
}
public IEditHandles Provide(Observable<Feature> feature, FeatureEditContext context, IFeatureEditCallback featureEditCallback)
{
var delegateHandles = _delegateHandlesProvider.Provide(feature, context, featureEditCallback);
return new CustomFeatureEditHandles(feature, featureEditCallback, delegateHandles, context);
}
}
object CustomFeatureHandlesProvider : IFeatureHandlesProvider {
private val delegateHandlesProvider = FeatureHandlesProvider()
override fun canProvide(feature: Observable<Feature>, context: FeatureEditContext): Boolean {
return delegateHandlesProvider.canProvide(feature, context)
}
override fun provide(
feature: Observable<Feature>,
context: FeatureEditContext,
featureEditCallback: IFeatureEditCallback
): IEditHandles? {
return delegateHandlesProvider.provide(feature, context, featureEditCallback)?.let {
CustomFeatureEditHandles(feature, featureEditCallback, it, context)
}
}
}
Step 3 - Use the custom feature handles provider
We need to make sure that the editing framework uses our custom handles provider, so we must configure it
in an IFeatureEditConfiguration
IFeatureEditConfiguration
IFeatureEditConfiguration
, and set that configuration
on the FeatureLayer
FeatureLayer
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 2")
.editConfiguration(customEditConfiguration)
.painter(std::make_shared<TransparencyFeaturePainter>())
.build();
// 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 2")
.EditConfiguration(customEditConfiguration)
.Painter(new TransparencyFeaturePainter())
.Build();
// 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 2")
.editConfiguration(customEditConfiguration)
.painter(TransparencyFeaturePainter)
.build()