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
CircleByCenterPoint
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.
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 CircleByCenterPoint
CircleByCenterPoint
CircleByCenterPoint
geometry, we create a new geometry creator
geometry creator
geometry creator
implementation. We implement these methods:
-
IGeometryHandlesProvider::start
IGeometryHandlesProvider::start
IGeometryHandlesProvider::start
: starts the geometry creation and provides you with a callback to signal changes in the geometry. -
IGeometryCreator::onEvent
IGeometryCreator::onEvent
IGeometryCreator::onEvent
: provides you with input events that you can use to determine what happens with the geometry creation.
This sample code shows how you can do that:
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 FeatureCreator
FeatureCreator
FeatureCreator
.
Note that you can also implement your own feature creator by implementing IFeatureCreator
IFeatureCreator
IFeatureCreator
.
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.
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
}