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 CircleByCenterPointCircleByCenterPointCircleByCenterPoint. 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.

We also added a way to create the circle by using a touch screen and a drag gesture. If this mode is used the sequence of creation is the following:

  • The start of the gesture defines the center point.

  • The radius follows the input movement until the gesture is ended.

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 CircleByCenterPointCircleByCenterPointCircleByCenterPoint geometry, we create a new geometry creatorgeometry creatorgeometry creator implementation. We implement these methods:

This sample code shows how you can do that:

Program: 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 && _input == Input::Mouse) {
    auto point = moveToViewLocation(moveEvent->getLocation(), context->getMap());
    onMove(point);
    return EventResult::Consumed;
  }

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

  auto* dragEvent = dynamic_cast<DragEvent*>(inputEvent.get());
  if (dragEvent) {
    if (dynamic_cast<MouseDragEvent*>(inputEvent.get())) {
      _input = Input::Mouse;
    } else if (dynamic_cast<TouchDragEvent*>(inputEvent.get())) {
      _input = Input::Touch;
    }
    auto point = moveToViewLocation(dragEvent->getLocation(), context->getMap());
    if (dragEvent->getStatus() == EventStatus::Start || dragEvent->getStatus() == EventStatus::End) {
      onClick(point);
      return EventResult::Consumed;
    }
    onMove(point);
    return EventResult::Consumed;
  }

  return luciad::EventResult::NotConsumed;
}
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:
        {
            if (_input != Input.Mouse)
            {
                return EventResult.NotConsumed;
            }
            var point = MoveToViewLocation(moveEvent.Location, context.Map);
            OnMove(point);
            return EventResult.Consumed;
        }
        case ClickEvent clickEvent when clickEvent.ClickCount == 1:
        {
            _input = clickEvent is MouseClickEvent ? Input.Mouse : Input.Touch;
            var point = MoveToViewLocation(clickEvent.Location, context.Map);
            OnClick(point);
            return EventResult.Consumed;
        }
        case DragEvent dragEvent:
        {
            _input = dragEvent is MouseDragEvent ? Input.Mouse : Input.Touch;
            var point = MoveToViewLocation(dragEvent.Location, context.Map);
            if (dragEvent.Status == EventStatus.Start || dragEvent.Status == EventStatus.End)
            {
                OnClick(point);
                return EventResult.Consumed;
            }
            OnMove(point);
            return EventResult.Consumed;
        }
        default:
            return EventResult.NotConsumed;
    }
}
override fun start(callback: IGeometryCreateCallback, context: FeatureCreateContext) {
    this.callback = callback
    isAdjustingCenter = true
    finished = false
}

private fun onGeometryChange(point: Point, changeStatus: ChangeStatus) {
    val geodesy = GeodesyCalculations.create(reference, LineInterpolationType.Geodesic)
    val dist = geodesy.distance2D(centerPoint!!.location, point.location)
    if (dist != null) {
        val circleByCenterPoint = GeometryFactory.createCircleByCenterPoint(
            reference, centerPoint!!.location, dist
        )
        callback!!.onGeometryChanged(circleByCenterPoint, changeStatus)
    }
}

private fun onMove(point: Point?) {
    if (point == null || finished) {
        return
    }
    if (isAdjustingCenter) {
        centerPoint = point
    }
    onGeometryChange(point, ChangeStatus.InProgress)
}

private fun 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)
}

override fun onEvent(inputEvent: IInputEvent, context: FeatureCreateContext): EventResult {
    return when (inputEvent) {
        is ClickEvent -> {
            if (inputEvent.clickCount != 1L) {
                return EventResult.NotConsumed
            }
            val point = moveToViewLocation(inputEvent.location, context.map)
            onClick(point)
            EventResult.Consumed
        }
        is DragEvent -> {
            val point = moveToViewLocation(inputEvent.location, context.map)
            if (inputEvent.status == EventStatus.Start || inputEvent.status == EventStatus.End) {
                onClick(point)
                return EventResult.Consumed
            }
            onMove(point)
            EventResult.Consumed
        }
        else -> {
            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 FeatureCreatorFeatureCreatorFeatureCreator.

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

Program: Create a feature creator
  std::shared_ptr<Creator> createFeatureCreator(std::shared_ptr<IGeometryCreator> geometryCreator,
                                                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(std::move(creationLayer)).map(map).featureCreator(std::move(featureCreator)).build();
  }
std::shared_ptr<luciad::Creator> CreatorFactory::createCircleByCenterPointCreator(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, std::move(creationLayer), map);
}
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();
}
fun createCircleByCenterPointCreator(creationLayer: FeatureLayer, map: Map): Creator {
    // Determine the reference in which the created geometry will be defined
    val reference: CoordinateReference = getCreationReference(creationLayer)

    // Make sure a CircleByCenterPoint is created
    val circleByCenterPointCreator = CircleByCenterPointCreator(reference)

    // Make sure a Feature is created using the new CircleByCenterPoint instance
    return createFeatureCreator(circleByCenterPointCreator, creationLayer, map)
}

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: 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;
}
private bool StartCreation(Creator creator)
{

    if (!(Map.Controller is DefaultController defaultController))
    {
        return false;
    }

    var observer = new MyCreatorObserver(this);
    creator.AddObserver(observer);
    defaultController.Creator = creator;
    return true;
}
private fun enableFeatureCreation(creator: Creator): Boolean {
    val defaultController = map.controller as? DefaultController
        ?: return false
    creator.addObserver(CreatorObserver())
    defaultController.setCreator(creator)
    return true
}