This article describes how to apply constraints to the geometry editing and creation process.
We configure this Polyline
Polyline
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
IPolylineConstraint
IPolylineConstraint
and make sure that the feature handles providerfeature handles providerfeature handles provider and the creatorcreatorcreator use it.
Step 1- Implement the constraint
The constraint interface has two methods to implement:
-
A general
apply
apply
apply
method that takes only onePolyline
Polyline
Polyline
parameter. The application calls this method when there is no particular change that prompted the constraint call. For example, the default LuciadCPillar geometry creatorsgeometry creatorsgeometry creators call it while the geometry is still being created. -
An
apply
apply
apply
method that takes both an old polyline parameter with the original polyline and a newPolyline
Polyline
Polyline
parameter, which is the polyline resulting from a change, without constraints applied. It also takes aPolylineChange
PolylineChange
PolylineChange
object parameter that describes the change that turned the old polyline into the new polyline.
The method returns thePolyline
Polyline
Polyline
that results from applying the constraint. Editors created byPolylineHandlesProvider
PolylineHandlesProvider
PolylineHandlesProvider
— and by the other default geometry handles providergeometry handles providergeometry 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
Polyline
Polyline
, the newPolyline
Polyline
Polyline
, 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;
};
public class MyPolylineConstraint : IPolylineConstraint
{
private double maxHeight = 100e3;
public Polyline Apply(Polyline oldPolyline, Polyline newPolyline, PolylineChange changes)
{
return this.ApplyImpl(newPolyline);
}
public Polyline Apply(Polyline polyline)
{
return ApplyImpl(polyline);
}
private Polyline ApplyImpl(Polyline polyline)
{
// 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.
var bounds = polyline.Bounds;
if (bounds.Location.Z + bounds.Depth <= maxHeight)
{
return polyline;
}
// At least one point of the polyline lies above the max height, so adjust the Z-values and recreate the polyline.
var points = new List<Coordinate>(polyline.Points.Count);
foreach (var point in polyline.Points)
{
double z = Math.Min(point.Z, maxHeight);
points.Add(new Coordinate(point.X, point.Y, z));
}
return GeometryFactory.CreatePolyline(polyline.Reference, points, polyline.InterpolationType);
}
}
public static class MyPolylineConstraint1 implements IPolylineConstraint {
private double maxHeight = 100e3;
@Override
public Polyline apply(Polyline oldPolyline, Polyline newPolyline, PolylineChange changes) {
return this.applyImpl(newPolyline);
}
@Override
public Polyline apply(Polyline polyline) {
return applyImpl(polyline);
}
private Polyline applyImpl(Polyline polyline) {
// 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.
Bounds bounds = polyline.getBounds();
if (bounds.getLocation().getZ() + 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.
List<Coordinate> points = new ArrayList<>();
for (Coordinate point : polyline.getPoints()) {
double z = Math.min(point.getZ(), maxHeight);
points.add(new Coordinate(point.getX(), point.getY(), z));
}
return GeometryFactory.createPolyline(polyline.getReference(), points, polyline.getInterpolationType());
}
}
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
PolylineChange
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;
};
public class MyPolylineConstraint : IPolylineConstraint
{
private double maxHeight = 100e3;
public Polyline Apply(Polyline oldPolyline, Polyline newPolyline, PolylineChange changes)
{
// If the last point was moved with this change, we need to adjust the first point, instead of the last.
return this.ApplyImpl(newPolyline, changes.HasPointMoved(newPolyline.PointCount - 1));
}
public Polyline Apply(Polyline polyline)
{
// There is no information about any change that happened, so we adjust the last point by default.
return ApplyImpl(polyline, false);
}
private Polyline ApplyImpl(Polyline polyline, bool adjustFirstPoint)
{
uint lastPointIndex = polyline.PointCount - 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.
uint pointToAdjust = adjustFirstPoint ? 0u : lastPointIndex;
Coordinate location = polyline.GetPoint(pointToAdjust);
Coordinate newLocation = new Coordinate(location.X,
polyline.GetPoint(lastPointIndex - pointToAdjust).Y, location.Z);
polyline = polyline.MovePoint(pointToAdjust, newLocation);
}
// 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.
var bounds = polyline.Bounds;
if (bounds.Location.Z + bounds.Depth <= maxHeight)
{
return polyline;
}
// At least one point of the polyline lies above the max height, so adjust the Z-values and recreate the polyline.
var points = new List<Coordinate>(polyline.Points.Count);
foreach (var point in polyline.Points)
{
double z = Math.Min(point.Z, maxHeight);
points.Add(new Coordinate(point.X, point.Y, z));
}
return GeometryFactory.CreatePolyline(polyline.Reference, points, polyline.InterpolationType);
}
}
public static class MyPolylineConstraint implements IPolylineConstraint {
private double maxHeight = 100e3;
@Override
public Polyline apply(Polyline oldPolyline, Polyline newPolyline, PolylineChange changes) {
// If the last point was moved with this change, we need to adjust the first point, instead of the last.
return this.applyImpl(newPolyline, changes.hasPointMoved(newPolyline.getPointCount() - 1));
}
@Override
public Polyline apply(Polyline polyline) {
// There is no information about any change that happened, so we adjust the last point by default.
return applyImpl(polyline, false);
}
private Polyline applyImpl(Polyline polyline, boolean adjustFirstPoint) {
long lastPointIndex = polyline.getPointCount() - 1;
if (polyline.getPoint(0).getY() != polyline.getPoint(lastPointIndex).getY()) {
// if the last point was moved, adjust the first point. Otherwise adjust the last point.
long pointToAdjust = adjustFirstPoint ? 0 : lastPointIndex;
Coordinate location = polyline.getPoint(pointToAdjust);
Coordinate newLocation = new Coordinate(location.getX(),
polyline.getPoint(lastPointIndex - pointToAdjust).getY(), location.getZ());
polyline = polyline.movePoint(pointToAdjust, newLocation);
}
// 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.
Bounds bounds = polyline.getBounds();
if (bounds.getLocation().getZ() + 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.
List<Coordinate> points = new ArrayList<>();
for (Coordinate point : polyline.getPoints()) {
double z = Math.min(point.getZ(), maxHeight);
points.add(new Coordinate(point.getX(), point.getY(), z));
}
return GeometryFactory.createPolyline(polyline.getReference(), points, polyline.getInterpolationType());
}
}
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
PolylineCreator::setConstraint
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
IFeatureEditConfiguration
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();
}
};
class ConstraintEditConfiguration : IFeatureEditConfiguration
{
public void Edit(Feature feature, ulong layerId, Map map, FeatureEditConfigurationBuilder builder)
{
// create a new polyline handles provider on which we configure our custom constraint.
var PolylineHandlesProvider = new PolylineHandlesProvider {Constraint = new MyPolylineConstraint()};
// put the factory in a composite, with high priority, so it is used before the default factories.
var compositeProvider = CompositeGeometryHandlesProvider.CreateDefault();
compositeProvider.Add(PolylineHandlesProvider, Priority.High);
// Set the composite handles provider as delegate of a new Feature handles provider,
// and submit this to the edit configuration builder.
builder.HandlesProvider(new FeatureHandlesProvider {GeometryHandlesProvider = compositeProvider});
builder.Submit();
}
}
public static class ConstraintEditConfiguration implements IFeatureEditConfiguration {
@Override
public void edit(Feature feature, long layerId, Map map, FeatureEditConfigurationBuilder builder) {
// create a new polyline handles provider on which we configure our custom constraint.
PolylineHandlesProvider polylineHandlesProvider = new PolylineHandlesProvider();
polylineHandlesProvider.setConstraint(new MyPolylineConstraint());
// put the factory in a composite, with high priority, so it is used before the default factories.
CompositeGeometryHandlesProvider compositeProvider = CompositeGeometryHandlesProvider.createDefault();
compositeProvider.add(polylineHandlesProvider, Priority.High);
// Set the composite handles provider as delegate of a new Feature handles provider,
// and submit this to the edit configuration builder.
FeatureHandlesProvider featureHandlesProvider = new FeatureHandlesProvider();
featureHandlesProvider.setGeometryHandlesProvider(compositeProvider);
builder.handlesProvider(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);
var editConfiguration = new ConstraintEditConfiguration();
var layer = FeatureLayer.NewBuilder().Model(model).EditConfiguration(editConfiguration).Build();
map.LayerList.Add(layer);
ConstraintEditConfiguration editConfiguration = new ConstraintEditConfiguration();
FeatureLayer layer = FeatureLayer.newBuilder()
.model(model)
.editConfiguration(editConfiguration)
.build();
map.getLayerList().add(layer);