This tutorial explains how you can create your own geometry creator and use it to create a feature on the map.

The goal of this tutorial is to add support for the creation of a feature with a CircleByCenterPoint. We want to create that feature with two clicks on the map:

  • The first point defines the center point of the circle.

  • The second point determines the radius of the circle.

The sample_editing sample supports this tutorial. It’s available for C++ and C#.

See this article for more information about creation.

Step 1 - Implement a geometry creator

To add support for the creation of the CircleByCenterPoint geometry, we create a new geometry creator implementation. We implement these methods:

This sample code shows how you can do that:

Program (C++): Geometry creator for CircleByCenterPoint
void CircleByCenterPointCreator::start(std::shared_ptr<IGeometryCreateCallback> callback, const std::shared_ptr<luciad::FeatureCreateContext>& /*context*/) {
  _callback = std::move(callback);
  _isAdjustingCenter = true;
  _finished = false;
}

void CircleByCenterPointCreator::onGeometryChange(const Point& point, ChangeStatus changeStatus) {
  auto geodesy = GeodesyCalculations::create(_reference, LineInterpolationType::Geodesic);
  auto dist = geodesy->distance2D(_centerPoint.getLocation(), point.getLocation());
  if (dist) {
    std::shared_ptr<CircleByCenterPoint> circleByCenterPoint = GeometryFactory::createCircleByCenterPoint(_reference, _centerPoint.getLocation(), *dist);
    _callback->onGeometryChanged(circleByCenterPoint, changeStatus);
  }
}

void CircleByCenterPointCreator::onMove(const std::optional<Point>& point) {
  if (!point || _finished) {
    return;
  }

  if (_isAdjustingCenter) {
    _centerPoint = *point;
  }
  onGeometryChange(*point, ChangeStatus::InProgress);
}

void CircleByCenterPointCreator::onClick(const std::optional<Point>& point) {
  if (!point || _finished) {
    return;
  }
  if (_isAdjustingCenter) {
    _centerPoint = *point;
    _isAdjustingCenter = false;
    onGeometryChange(*point, ChangeStatus::InProgressKeyPoint);
    return;
  }

  _finished = true;
  onGeometryChange(*point, ChangeStatus::Finished);
}

EventResult CircleByCenterPointCreator::onEvent(const std::shared_ptr<IInputEvent>& inputEvent, const std::shared_ptr<FeatureCreateContext>& context) {
  auto* moveEvent = dynamic_cast<MouseMoveEvent*>(inputEvent.get());
  if (moveEvent) {
    auto point = moveToViewLocation(moveEvent->getLocation(), context->getMap());
    onMove(point);
    return EventResult::Consumed;
  }

  auto* clickEvent = dynamic_cast<ClickEvent*>(inputEvent.get());
  if (clickEvent && clickEvent->getClickCount() == 1) {
    auto point = moveToViewLocation(clickEvent->getLocation(), context->getMap());
    onClick(point);
    return EventResult::Consumed;
  }

  return luciad::EventResult::NotConsumed;
}

C#

public void Start(IGeometryCreateCallback callback, FeatureCreateContext context)
{
    _callback = callback;
    _isAdjustingCenter = true;
    _finished = false;
}

void OnGeometryChange(Point point, ChangeStatus changeStatus)
{
    var geodesy = GeodesyCalculations.Create(_reference, LineInterpolationType.Geodesic);
    var dist = geodesy.Distance2D(_centerPoint.Location, point.Location);
    if (dist != null)
    {
        CircleByCenterPoint circleByCenterPoint =
            GeometryFactory.CreateCircleByCenterPoint(_reference, _centerPoint.Location, dist.Value);
        _callback.OnGeometryChanged(circleByCenterPoint, changeStatus);
    }
}

void OnMove(Point point)
{
    if (point == null || _finished)
    {
        return;
    }

    if (_isAdjustingCenter)
    {
        _centerPoint = point;
    }

    OnGeometryChange(point, ChangeStatus.InProgress);
}

void OnClick(Point point)
{
    if (point == null || _finished)
    {
        return;
    }

    if (_isAdjustingCenter)
    {
        _centerPoint = point;
        _isAdjustingCenter = false;
        OnGeometryChange(point, ChangeStatus.InProgressKeyPoint);
        return;
    }

    _finished = true;
    OnGeometryChange(point, ChangeStatus.Finished);
}

public EventResult OnEvent(IInputEvent inputEvent, FeatureCreateContext context)
{
    switch (inputEvent)
    {
        case MouseMoveEvent moveEvent:
        {
            var point = MoveToViewLocation(moveEvent.Location, context.Map);
            OnMove(point);
            return EventResult.Consumed;
        }
        case ClickEvent clickEvent when clickEvent.ClickCount == 1:
        {
            var point = MoveToViewLocation(clickEvent.Location, context.Map);
            OnClick(point);
            return EventResult.Consumed;
        }
        default:
            return EventResult.NotConsumed;
    }
}

Step 2 - Create a feature creator using the circle-by-center-point geometry creator

Now we must create a feature based on the geometry. Here we use the available FeatureCreator.

Note that you can also implement your own feature creator by implementing IFeatureCreator.

Program (C++): Create a feature creator
  std::shared_ptr<Creator> createFeatureCreator(std::shared_ptr<IGeometryCreator> geometryCreator,
                                                const std::shared_ptr<FeatureLayer>& creationLayer,
                                                const std::shared_ptr<Map>& map) {
    // Create a FeatureCreator that uses the given IGeometryCreator to create its geometry
    auto featureCreator = std::make_shared<FeatureCreator>(createEmptyFeature(), std::move(geometryCreator));

    // Configure the FeatureCreator on Creator
    return Creator::newBuilder().layer(creationLayer).map(map).featureCreator(std::move(featureCreator)).build();
  }
std::shared_ptr<luciad::Creator> CreatorFactory::createCircleByCenterPointCreator(const std::shared_ptr<FeatureLayer>& creationLayer,
                                                                                  const std::shared_ptr<Map>& map) {
  // Determine the reference in which the created geometry will be defined
  auto reference = getCreationReference(creationLayer);

  // Make sure that a circle by center point is created
  auto circleCreator = std::make_shared<CircleByCenterPointCreator>(reference);

  // Make sure a Feature is created using the new circle by center point instance
  return createFeatureCreator(circleCreator, creationLayer, map);
}

C#

public static Creator CreateCircleByCenterPointCreator(FeatureLayer creationLayer, Map map)
{
    // Determine the reference in which the created geometry will be defined
    var reference = GetCreationReference(creationLayer);

    // Make sure a CircleByCenterPoint is created
    var circleByCenterPointCreator = new CircleByCenterPointCreator(reference);

    // Make sure a Feature is created using the new CircleByCenterPoint instance
    return CreateFeatureCreator(circleByCenterPointCreator, creationLayer, map);
}
private static Creator CreateFeatureCreator(IGeometryCreator geometryCreator, FeatureLayer creationLayer, Map map)
{
    // Create a FeatureCreator that uses the given IGeometryCreator to create its geometry
    var featureCreator = new FeatureCreator(CreateEmptyFeature(), geometryCreator);

    // Configure the FeatureCreator on Creator
    return Creator.NewBuilder().Layer(creationLayer).Map(map).FeatureCreator(featureCreator).Build();
}

Step 3 - Register the feature creator in the controller

The last step is to register the feature creator with the controller. It can then process the input events to make the geometry, and add the new feature to the model.

Program (C++): Enable feature creation
bool QtMapBackend::enableFeatureCreation(std::shared_ptr<Creator> creator) {
  auto defaultController = std::dynamic_pointer_cast<DefaultController>(getMap()->getController());
  if (!defaultController) {
    return false;
  }

  // Adds an observer that makes sure the UI is reset to its normal state after creation ends
  auto creatorObserver = std::make_shared<CreatorObserver>(this);
  creator->addObserver(creatorObserver);

  // Enable creation in the default controller
  defaultController->setCreator(std::move(creator));
  return true;
}

C#

private bool StartCreation(Creator creator)
{
    if (!(Controller is DefaultController defaultController))
    {
        return false;
    }

    var observer = new MyCreatorObserver(this);
    creator.AddObserver(observer);
    defaultController.Creator = creator;
    return true;
}