This article describes how to apply constraints to the geometry editing and creation process.
We configure this Polyline
constraint as an example:
-
The Z-values of all polyline points have a maximum height.
-
The first and the last point of the polyline must always have the same Y-coordinate.
For this constraint, we must implement an IPolylineConstraint
and make sure that the feature handles provider and the creator use it.
Step 1- Implement the constraint
The constraint interface has two methods to implement:
-
A general
apply
method that takes only onePolyline
parameter. The application calls this method when there is no particular change that prompted the constraint call. For example, the default LuciadCPillar geometry creators call it while the geometry is still being created. -
An
apply
method that takes both an old polyline parameter with the original polyline and a newPolyline
parameter, which is the polyline resulting from a change, without constraints applied. It also takes aPolylineChange
object parameter that describes the change that turned the old polyline into the new polyline.
The method returns thePolyline
that results from applying the constraint. Editors created byPolylineHandlesProvider
— and by the other default geometry handles provider — always call this method rather than the general method. For example, when users move a polyline point with an edit handle, the application calls theapply
method of the constraint with the originalPolyline
, the newPolyline
, and a change object that describes the point move.
Let’s start by implementing the maximum height limitation.
We don’t need any information about the change to apply this constraint, so we add a general helper method and make both apply
methods delegate to it.
class MyPolylineConstraint : public IPolylineConstraint { public: std::shared_ptr<Polyline> apply(const std::shared_ptr<Polyline>& /*oldPolyline*/, const std::shared_ptr<Polyline>& newPolyline, const PolylineChange& /*changes*/) override { return this->applyImpl(newPolyline); } std::shared_ptr<Polyline> apply(const std::shared_ptr<Polyline>& polyline) override { return this->applyImpl(polyline); } private: std::shared_ptr<Polyline> applyImpl(const std::shared_ptr<Polyline>& polyline) const { // To avoid unnecessary copies, we first check the polyline's bounding box. // If the bounds don't surpass the max height, we don't need to apply any changes and simply return the given polyline. auto bounds = polyline->getBounds(); if (bounds.getLocation().z + bounds.getDepth() <= _maxheight) { return polyline; } // At least one point of the polyline lies above the max height, so adjust the Z-values and recreate the polyline. auto points = polyline->getPoints(); for (auto& point : points) { point.z = std::min(point.z, _maxheight); } return GeometryFactory::createPolyline(polyline->getReference(), std::move(points), polyline->getInterpolationType()); } double _maxheight = 100e3; };
For the second constraint, we need information about the change that happened: when the first point moves, we want to move
the last point along, and the other way around.
To find out which point changed, we can use the PolylineChange
parameter.
class MyPolylineConstraint : public IPolylineConstraint { public: std::shared_ptr<Polyline> apply(const std::shared_ptr<Polyline>& /*oldPolyline*/, const std::shared_ptr<Polyline>& newPolyline, const PolylineChange& changes) override { // If the last point was moved with this change, we need to adjust the first point to this new location, instead of the other way around. return this->applyImpl(newPolyline, changes.hasPointMoved(newPolyline->getPointCount() - 1)); } std::shared_ptr<Polyline> apply(const std::shared_ptr<Polyline>& polyline) override { // There is no information about any change that happened, so we adjust the last point by default. return this->applyImpl(polyline, false); } private: std::shared_ptr<Polyline> applyImpl(std::shared_ptr<Polyline> polyline, bool adjustFirstPoint) const { size_t lastPointIndex = polyline->getPointCount() - 1; if (polyline->getPoint(0).y != polyline->getPoint(lastPointIndex).y) { // if the last point was moved, adjust the first point. Otherwise adjust the last point. size_t pointToAdjust = adjustFirstPoint ? 0 : lastPointIndex; Coordinate location = polyline->getPoint(pointToAdjust); location.y = polyline->getPoint(lastPointIndex - pointToAdjust).y; polyline = polyline->movePoint(pointToAdjust, location); } // To avoid unnecessary copies, we first check the polyline's bounding box. // If the bounds don't surpass the max height, we don't need to apply any changes and can simply return the given polyline. auto bounds = polyline->getBounds(); if (bounds.getLocation().z + bounds.getDepth() <= _maxheight) { return polyline; } // At least one point of the polyline lies above the max height, so adjust the Z-values and recreate the polyline. auto points = polyline->getPoints(); for (auto& point : points) { point.z = std::min(point.z, _maxheight); } return GeometryFactory::createPolyline(polyline->getReference(), std::move(points), polyline->getInterpolationType()); } double _maxheight = 100e3; };
Step 2 - Configure the constraint on the editor or creator
We created our custom constraint in step 1. Now, we make sure it’s actually used.
For geometry creators, we can set the constraint directly on the specific creator class.
See PolylineCreator::setConstraint
for example.
To apply the constraint when users are editing the polyline, we need extra configuration.
We must set the constraint on the appropriate geometry factory. Then, we must configure it on the feature layer.
We can do so with the IFeatureEditConfiguration
.
The geometry handles providers offer more configuration options than just constraints.
On the |
class ConstraintEditConfiguration : public IFeatureEditConfiguration { public: void edit(const Feature& /*feature*/, LayerId /*layerId*/, const std::shared_ptr<Map>& /*map*/, FeatureEditConfigurationBuilder& builder) const override { // create a new polyline handles provider on which we configure our custom constraint. auto polylineHandlesProvider = std::make_shared<PolylineHandlesProvider>(); polylineHandlesProvider->setConstraint(std::make_shared<MyPolylineConstraint>()); // put the handles provider in a composite, with high priority, so it is used before the default factories. auto compositeProvider = CompositeGeometryHandlesProvider::createDefault(); compositeProvider->add(std::move(polylineHandlesProvider), Priority::high()); // Set the composite provider as delegate of a new Feature handles provider, and submit this to the edit configuration builder. auto featureHandlesProvider = std::make_shared<FeatureHandlesProvider>(); featureHandlesProvider->setGeometryHandlesProvider(std::move(compositeProvider)); builder.handlesProvider(std::move(featureHandlesProvider)); builder.submit(); } };
To complete step 2, we give this edit configuration to the FeatureLayer::Builder
.
auto editConfiguration = std::make_shared<ConstraintEditConfiguration>(); auto layer = FeatureLayer::newBuilder().model(model).editConfiguration(editConfiguration).build(); map->getLayerList()->add(layer);