Use the geometry editor to see information about the geometry editor’s previewed geometry during an editing interaction.

Use case
The geometry editor can provide information about the geometry being created or edited during an interaction. This information can be used to give feedback to the user to show the effect of the interaction on the geometry.
How to use the sample
Tap a graphic to edit its geometry by moving, rotating, or scaling the geometry. During the interaction, information about the geometry will be displayed to provide feedback to the user.
Use the buttons in the settings view to undo or redo changes made to the geometry, and use the Discard edits and Save edits buttons to discard or save changes, respectively.
How it works
- Create a
GeometryEditorand set it on theMapView. - Connect to
GeometryEditor::interactionPreviewChangedto receive aGeometryEditorInteractionPreviewduring an interaction. - Use
GeometryEditorInteractionPreview::previewGeometryandGeometryEditorInteractionPreview::interactionTypeto display information about move, rotate, and scale interactions. - Identify a
GraphicwithMapView::identifyGraphicsOverlayAsyncand callGeometryEditor::startwith its geometry. - Use
GeometryEditor::undoandGeometryEditor::redoto change the edit history. - Call
GeometryEditor::stopand apply the returned geometry to the edited graphic to save the result.
Relevant API
- Geometry
- GeometryEditor
- GeometryEditorInteractionPreview
- GeometryEditorInteractionType
- Graphic
- GraphicsOverlay
Additional information
The GeometryEditor::interactionPreviewChanged signal is emitted continuously during an interaction, so it should not be used to trigger resource-intensive operations.
Tags
draw, edit, geometry editor, interaction preview
Sample code
// [WriteFile Name=DisplayGeometryEditorInformationDuringInteraction, Category=EditData]// [Legal]// Copyright 2026 Esri.//// Licensed under the Apache License, Version 2.0 (the "License");// you may not use this file except in compliance with the License.// You may obtain a copy of the License at// http://www.apache.org/licenses/LICENSE-2.0//// Unless required by applicable law or agreed to in writing, software// distributed under the License is distributed on an "AS IS" BASIS,// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.// See the License for the specific language governing permissions and// limitations under the License.// [Legal]
#ifdef PCH_BUILD#include "pch.hpp"#endif // PCH_BUILD
// sample headers#include "DisplayGeometryEditorInformationDuringInteraction.h"
// ArcGIS Maps SDK headers#include "Envelope.h"#include "ErrorException.h"#include "Geometry.h"#include "GeometryEditor.h"#include "GeometryEditorInteractionPreview.h"#include "GeometryEditorTypes.h"#include "GeometryTypes.h"#include "Graphic.h"#include "GraphicListModel.h"#include "GraphicsOverlay.h"#include "GraphicsOverlayListModel.h"#include "IdentifyGraphicsOverlayResult.h"#include "ImmutablePart.h"#include "ImmutablePartCollection.h"#include "ImmutablePointCollection.h"#include "InteractionConfiguration.h"#include "Map.h"#include "MapQuickView.h"#include "MapTypes.h"#include "Multipoint.h"#include "Point.h"#include "Polygon.h"#include "Polyline.h"#include "SimpleFillSymbol.h"#include "SimpleLineSymbol.h"#include "SimpleMarkerSymbol.h"#include "SymbolTypes.h"#include "VertexTool.h"#include "Viewpoint.h"
// Qt headers#include <QFuture>#include <QtMath>
// STL headers#include <cmath>#include <memory>
using namespace Esri::ArcGISRuntime;
DisplayGeometryEditorInformationDuringInteraction::DisplayGeometryEditorInformationDuringInteraction(QObject* parent /* = nullptr */) : QObject(parent), m_map(new Map(BasemapStyle::ArcGISStreets, this)), m_graphicsOverlay(new GraphicsOverlay(this)), m_geometryEditor(new GeometryEditor(this)){ m_map->setInitialViewpoint(Viewpoint::fromJson( R"({"rotation":0.0,"scale":17000,"targetGeometry":{"spatialReference":{"wkid":3857},"x":-13045202.018086127,"y":4035612.571361517}})"));
VertexTool* vertexTool = new VertexTool(this); InteractionConfiguration* configuration = new InteractionConfiguration(this); configuration->setAllowVertexCreation(false); configuration->setAllowMidVertexSelection(false); configuration->setAllowDeletingSelectedElement(false); configuration->setAllowVertexSelection(false); configuration->setAllowPartCreation(false); vertexTool->setConfiguration(configuration); m_geometryEditor->setTool(vertexTool);
createInitialGraphics();
connect(m_geometryEditor, &GeometryEditor::isStartedChanged, this, &DisplayGeometryEditorInformationDuringInteraction::geometryEditorStartedChanged); connect(m_geometryEditor, &GeometryEditor::canUndoChanged, this, &DisplayGeometryEditorInformationDuringInteraction::canUndoChanged); connect(m_geometryEditor, &GeometryEditor::canRedoChanged, this, &DisplayGeometryEditorInformationDuringInteraction::canRedoChanged); connect(m_geometryEditor, &GeometryEditor::interactionPreviewChanged, this, &DisplayGeometryEditorInformationDuringInteraction::onInteractionPreviewChanged);}
DisplayGeometryEditorInformationDuringInteraction::~DisplayGeometryEditorInformationDuringInteraction() = default;
void DisplayGeometryEditorInformationDuringInteraction::init(){ qmlRegisterType<MapQuickView>("Esri.Samples", 1, 0, "MapView"); qmlRegisterType<DisplayGeometryEditorInformationDuringInteraction>("Esri.Samples", 1, 0, "DisplayGeometryEditorInformationDuringInteractionSample");}
void DisplayGeometryEditorInformationDuringInteraction::setMapView(MapQuickView* mapView){ if (!mapView || mapView == m_mapView) { return; }
m_mapView = mapView; m_mapView->setMap(m_map); m_mapView->graphicsOverlays()->append(m_graphicsOverlay); m_mapView->setGeometryEditor(m_geometryEditor);
connect(m_mapView, &MapQuickView::mouseClicked, this, &DisplayGeometryEditorInformationDuringInteraction::onMapClicked); emit mapViewChanged();}
MapQuickView* DisplayGeometryEditorInformationDuringInteraction::mapView() const{ return m_mapView;}
void DisplayGeometryEditorInformationDuringInteraction::createInitialGraphics(){ const Polygon polygon = geometry_cast<Polygon>(Geometry::fromJson( R"({"rings":[[[-13046991.222211758,4034618.5047884779],[-13046991.222211758,4035962.0723415823],[-13045677.652220398,4035962.0723415823],[-13045677.652220398,4034618.5047884779],[-13046991.222211758,4034618.5047884779]]],"spatialReference":{"wkid":3857}})")); const Polyline polyline = geometry_cast<Polyline>(Geometry::fromJson( R"({"paths":[[[-13044533.805088846,4034221.5100018946],[-13043597.938505623,4034197.1337576872],[-13043597.938505623,4035135.572073034],[-13044522.634505576,4035170.5449295067]]],"spatialReference":{"wkid":3857}})")); const Multipoint multipoint = geometry_cast<Multipoint>(Geometry::fromJson( R"({"points":[[-13045283.292102993,4035739.1925106063],[-13045314.922186911,4036533.8852012255],[-13044798.24723932,4036138.7808295386],[-13044354.514637273,4035719.3623426706],[-13044281.57229173,4036473.0999132735]],"spatialReference":{"wkid":3857}})"));
SimpleLineSymbol* redOutline = new SimpleLineSymbol(SimpleLineSymbolStyle::Solid, QColor(255, 0, 0), 2, this); SimpleFillSymbol* polygonSymbol = new SimpleFillSymbol(SimpleFillSymbolStyle::Solid, QColor(255, 0, 0, 75), redOutline, this); SimpleMarkerSymbol* pointSymbol = new SimpleMarkerSymbol(SimpleMarkerSymbolStyle::Circle, QColor(0, 0, 255), 8, this);
m_graphicsOverlay->graphics()->append({ new Graphic(multipoint, pointSymbol, this), new Graphic(polygon, polygonSymbol, this), new Graphic(polyline, redOutline, this), });}
void DisplayGeometryEditorInformationDuringInteraction::onMapClicked(const QMouseEvent& mouseEvent){ if (m_geometryEditor->isStarted()) { return; }
m_mapView->identifyGraphicsOverlayAsync(m_graphicsOverlay, mouseEvent.position(), 10, false, this) .then(this, [this](IdentifyGraphicsOverlayResult* rawResult) { const std::unique_ptr<IdentifyGraphicsOverlayResult> result(rawResult); if (!result || result->graphics().isEmpty()) { return; }
m_editingGraphic = result->graphics().first(); m_geometryEditor->start(m_editingGraphic->geometry()); m_geometryEditor->selectGeometry(); m_editingGraphic->setVisible(false); }) .onFailed(this, [](const ErrorException& error) { qDebug() << "Identify failed:" << error.error().message(); });}
void DisplayGeometryEditorInformationDuringInteraction::onInteractionPreviewChanged(GeometryEditorInteractionPreview* interactionPreview){ if (!interactionPreview) { clearInteractionInformation(); return; }
// The preview is parented to GeometryEditor, so it would be cleaned up automatically. // We can delete it earlier after this callback because we do not retain it beyond this update. interactionPreview->deleteLater();
switch (interactionPreview->interactionType()) { case GeometryEditorInteractionType::Scale: setScaleInformation(interactionPreview->previewGeometry()); break; case GeometryEditorInteractionType::Rotate: setRotationInformation(interactionPreview->previewGeometry()); break; case GeometryEditorInteractionType::Move: setMoveInformation(interactionPreview->previewGeometry()); break; case GeometryEditorInteractionType::Create: clearInteractionInformation(); return; }
m_interactionInfoVisible = true; emit interactionInfoChanged();}
void DisplayGeometryEditorInformationDuringInteraction::setScaleInformation(const Geometry& previewGeometry){ const Envelope originalExtent = m_geometryEditor->geometry().extent(); const Envelope previewExtent = previewGeometry.extent(); if (qFuzzyIsNull(originalExtent.width()) || qFuzzyIsNull(originalExtent.height())) { clearInteractionInformation(); return; }
const double scaleX = previewExtent.width() / originalExtent.width(); const double scaleY = previewExtent.height() / originalExtent.height(); m_interactionDescription = tr("Scale factor (X, Y):"); m_interactionValue = QString("(%1, %2)").arg(QString::number(scaleX, 'f', 2), QString::number(scaleY, 'f', 2));}
void DisplayGeometryEditorInformationDuringInteraction::setRotationInformation(const Geometry& previewGeometry){ const Point center = m_geometryEditor->geometry().extent().center(); const Point originalPoint = firstPoint(m_geometryEditor->geometry()); const Point previewPoint = firstPoint(previewGeometry); if (originalPoint.isEmpty() || previewPoint.isEmpty()) { clearInteractionInformation(); return; }
const double vector1X = originalPoint.x() - center.x(); const double vector1Y = originalPoint.y() - center.y(); const double vector2X = previewPoint.x() - center.x(); const double vector2Y = previewPoint.y() - center.y(); const double crossProduct = (vector1X * vector2Y) - (vector1Y * vector2X); const double dotProduct = (vector1X * vector2X) + (vector1Y * vector2Y); const double angle = qRadiansToDegrees(std::atan2(crossProduct, dotProduct)); const double clockwiseAngle = std::fmod(-angle + 360.0, 360.0);
m_interactionDescription = tr("Rotation angle (degrees):"); m_interactionValue = QString::number(clockwiseAngle, 'f', 2);}
void DisplayGeometryEditorInformationDuringInteraction::setMoveInformation(const Geometry& previewGeometry){ const Point center = previewGeometry.extent().center(); m_interactionDescription = tr("Center (X, Y):"); m_interactionValue = QString("(%1, %2)").arg(QString::number(center.x(), 'f', 2), QString::number(center.y(), 'f', 2));}
Point DisplayGeometryEditorInformationDuringInteraction::firstPoint(const Geometry& geometry){ switch (geometry.geometryType()) { case GeometryType::Polygon: { const Polygon polygon = geometry_cast<Polygon>(geometry); return polygon.parts().isEmpty() ? Point() : polygon.parts().part(0).startPoint(); } case GeometryType::Polyline: { const Polyline polyline = geometry_cast<Polyline>(geometry); return polyline.parts().isEmpty() ? Point() : polyline.parts().part(0).startPoint(); } case GeometryType::Multipoint: { const Multipoint multipoint = geometry_cast<Multipoint>(geometry); return multipoint.points().isEmpty() ? Point() : multipoint.points().point(0); } default: return {}; }}
void DisplayGeometryEditorInformationDuringInteraction::undo(){ m_geometryEditor->undo();}
void DisplayGeometryEditorInformationDuringInteraction::redo(){ m_geometryEditor->redo();}
void DisplayGeometryEditorInformationDuringInteraction::cancelEdits(){ finishEditing(false);}
void DisplayGeometryEditorInformationDuringInteraction::saveEdits(){ finishEditing(true);}
void DisplayGeometryEditorInformationDuringInteraction::finishEditing(bool save){ if (!m_geometryEditor->isStarted()) { return; }
const Geometry result = m_geometryEditor->stop(); if (save && m_editingGraphic && !result.isEmpty()) { m_editingGraphic->setGeometry(result); }
if (m_editingGraphic) { m_editingGraphic->setVisible(true); m_editingGraphic = nullptr; } clearInteractionInformation();}
void DisplayGeometryEditorInformationDuringInteraction::clearInteractionInformation(){ if (!m_interactionInfoVisible && m_interactionDescription.isEmpty() && m_interactionValue.isEmpty()) { return; }
m_interactionInfoVisible = false; m_interactionDescription.clear(); m_interactionValue.clear(); emit interactionInfoChanged();}
bool DisplayGeometryEditorInformationDuringInteraction::geometryEditorStarted() const{ return m_geometryEditor->isStarted();}
bool DisplayGeometryEditorInformationDuringInteraction::canUndo() const{ return m_geometryEditor->canUndo();}
bool DisplayGeometryEditorInformationDuringInteraction::canRedo() const{ return m_geometryEditor->canRedo();}// [WriteFile Name=DisplayGeometryEditorInformationDuringInteraction, Category=EditData]// [Legal]// Copyright 2026 Esri.//// Licensed under the Apache License, Version 2.0 (the "License");// you may not use this file except in compliance with the License.// You may obtain a copy of the License at// http://www.apache.org/licenses/LICENSE-2.0//// Unless required by applicable law or agreed to in writing, software// distributed under the License is distributed on an "AS IS" BASIS,// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.// See the License for the specific language governing permissions and// limitations under the License.// [Legal]
#ifndef DISPLAYGEOMETRYEDITORINFORMATIONDURINGINTERACTION_H#define DISPLAYGEOMETRYEDITORINFORMATIONDURINGINTERACTION_H
// Qt headers#include <QObject>#include <QString>
class QMouseEvent;
namespace Esri::ArcGISRuntime{ class Geometry; class GeometryEditor; class GeometryEditorInteractionPreview; class Graphic; class GraphicsOverlay; class Map; class MapQuickView; class Point;} // namespace Esri::ArcGISRuntime
Q_MOC_INCLUDE("MapQuickView.h");
class DisplayGeometryEditorInformationDuringInteraction : public QObject{ Q_OBJECT
Q_PROPERTY(Esri::ArcGISRuntime::MapQuickView* mapView READ mapView WRITE setMapView NOTIFY mapViewChanged) Q_PROPERTY(bool geometryEditorStarted READ geometryEditorStarted NOTIFY geometryEditorStartedChanged) Q_PROPERTY(bool canUndo READ canUndo NOTIFY canUndoChanged) Q_PROPERTY(bool canRedo READ canRedo NOTIFY canRedoChanged) Q_PROPERTY(bool interactionInfoVisible MEMBER m_interactionInfoVisible NOTIFY interactionInfoChanged) Q_PROPERTY(QString interactionDescription MEMBER m_interactionDescription NOTIFY interactionInfoChanged) Q_PROPERTY(QString interactionValue MEMBER m_interactionValue NOTIFY interactionInfoChanged)
public: explicit DisplayGeometryEditorInformationDuringInteraction(QObject* parent = nullptr); ~DisplayGeometryEditorInformationDuringInteraction() override;
static void init();
Q_INVOKABLE void undo(); Q_INVOKABLE void redo(); Q_INVOKABLE void cancelEdits(); Q_INVOKABLE void saveEdits();
signals: void mapViewChanged(); void geometryEditorStartedChanged(); void canUndoChanged(); void canRedoChanged(); void interactionInfoChanged();
private: Esri::ArcGISRuntime::MapQuickView* mapView() const; void setMapView(Esri::ArcGISRuntime::MapQuickView* mapView);
bool geometryEditorStarted() const; bool canUndo() const; bool canRedo() const;
void createInitialGraphics(); void onMapClicked(const QMouseEvent& mouseEvent); void onInteractionPreviewChanged(Esri::ArcGISRuntime::GeometryEditorInteractionPreview* interactionPreview); void setScaleInformation(const Esri::ArcGISRuntime::Geometry& previewGeometry); void setRotationInformation(const Esri::ArcGISRuntime::Geometry& previewGeometry); void setMoveInformation(const Esri::ArcGISRuntime::Geometry& previewGeometry); void finishEditing(bool save); void clearInteractionInformation();
static Esri::ArcGISRuntime::Point firstPoint(const Esri::ArcGISRuntime::Geometry& geometry);
Esri::ArcGISRuntime::Map* m_map = nullptr; Esri::ArcGISRuntime::MapQuickView* m_mapView = nullptr; Esri::ArcGISRuntime::GraphicsOverlay* m_graphicsOverlay = nullptr; Esri::ArcGISRuntime::GeometryEditor* m_geometryEditor = nullptr; Esri::ArcGISRuntime::Graphic* m_editingGraphic = nullptr; QString m_interactionDescription; QString m_interactionValue; bool m_interactionInfoVisible = false;};
#endif // DISPLAYGEOMETRYEDITORINFORMATIONDURINGINTERACTION_H// [WriteFile Name=DisplayGeometryEditorInformationDuringInteraction, Category=EditData]// [Legal]// Copyright 2026 Esri.//// Licensed under the Apache License, Version 2.0 (the "License");// you may not use this file except in compliance with the License.// You may obtain a copy of the License at// http://www.apache.org/licenses/LICENSE-2.0//// Unless required by applicable law or agreed to in writing, software// distributed under the License is distributed on an "AS IS" BASIS,// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.// See the License for the specific language governing permissions and// limitations under the License.// [Legal]
import QtQuickimport QtQuick.Controlsimport QtQuick.Controls.Basic as Basicimport QtQuick.Layoutsimport Esri.Samples
Item { id: root
MapView { id: mapView anchors.fill: root
Component.onCompleted: forceActiveFocus() }
DisplayGeometryEditorInformationDuringInteractionSample { id: sampleModel mapView: mapView }
component AccentButton: Basic.Button { id: accentButton
display: AbstractButton.IconOnly icon.width: 32 icon.height: 32 icon.color: "#ffffff" opacity: enabled ? 1 : 0.3
background: Rectangle { implicitHeight: 48 color: accentButton.down ? Qt.darker(accentButton.palette.highlight, 1.2) : accentButton.hovered ? Qt.lighter(accentButton.palette.highlight, 1.1) : accentButton.palette.highlight } }
Control { readonly property real maximumWidth: Math.max(0, Math.min(380, root.width - 20))
width: sampleModel.geometryEditorStarted ? maximumWidth : Math.min(maximumWidth, promptLabel.implicitWidth + leftPadding + rightPadding) padding: 10
anchors { top: root.top right: root.right margins: 10 }
background: Rectangle { color: palette.base opacity: 0.9
MouseArea { anchors.fill: parent acceptedButtons: Qt.LeftButton | Qt.RightButton onClicked: mouse => mouse.accepted = true onDoubleClicked: mouse => mouse.accepted = true onWheel: wheel => wheel.accepted = true } }
contentItem: ColumnLayout { spacing: 8
Label { id: promptLabel
visible: !sampleModel.geometryEditorStarted text: qsTr("Tap a graphic to start the geometry editor.") wrapMode: Text.WordWrap
Layout.fillWidth: true }
GridLayout { columns: 2 visible: sampleModel.geometryEditorStarted
AccentButton { text: qsTr("Save edits") icon.source: "qrc:/Samples/EditData/DisplayGeometryEditorInformationDuringInteraction/check-circle-24.svg"
Layout.fillWidth: true
ToolTip.visible: hovered ToolTip.text: text
onClicked: sampleModel.saveEdits() }
AccentButton { text: qsTr("Discard edits") icon.source: "qrc:/Samples/EditData/DisplayGeometryEditorInformationDuringInteraction/circle-disallowed-24.svg"
Layout.fillWidth: true
ToolTip.visible: hovered ToolTip.text: text
onClicked: sampleModel.cancelEdits() }
AccentButton { text: qsTr("Undo") enabled: sampleModel.canUndo icon.source: "qrc:/Samples/EditData/DisplayGeometryEditorInformationDuringInteraction/undo-24.svg"
Layout.fillWidth: true
ToolTip.visible: hovered ToolTip.text: text
onClicked: sampleModel.undo() }
AccentButton { text: qsTr("Redo") enabled: sampleModel.canRedo icon.source: "qrc:/Samples/EditData/DisplayGeometryEditorInformationDuringInteraction/redo-24.svg"
Layout.fillWidth: true
ToolTip.visible: hovered ToolTip.text: text
onClicked: sampleModel.redo() } }
RowLayout { visible: sampleModel.interactionInfoVisible
Layout.fillWidth: true
Label { text: sampleModel.interactionDescription wrapMode: Text.WordWrap }
Label { text: sampleModel.interactionValue // ensure the size of the text displayed doesn't // judder as the numbers rapidly change on interacting font.features: ({ "tnum": 1 }) } } } }}