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.

Step 1- Implement the constraint

The constraint interface has two methods to implement:

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.

Program: The IPolylineConstraint with only the height limit.
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.

Program: The complete IPolylineConstraint.
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 PolylineHandlesProvider, for example, you can set the minimum or maximum number of points a polyline can have, or override which edit handles it provides.

Program: Setting a geometry constraint through an IFeatureEditConfiguration.
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.

Program: Provide the IFeatureEditConfiguration to a FeatureLayerBuilder
auto editConfiguration = std::make_shared<ConstraintEditConfiguration>();
auto layer = FeatureLayer::newBuilder().model(model).editConfiguration(editConfiguration).build();
map->getLayerList()->add(layer);