This sample demonstrates how to use the Sketch Editor to edit or sketch a new point, line, or polygon geometry on to a map.

Use case
A field worker could annotate features of interest on a map (via the GUI) such as location of dwellings (marked as points), geological features (polylines), or areas of glaciation (polygons).
How to use the sample
Choose which geometry type to sketch from one of the available buttons. Choose from points, multipoints, polylines, and polygons.
Add points or vertices by tapping on the map. You can edit individual points by tapping to select the point, then dragging it to a new location or double tapping to delete it.
Use the control panel to undo or redo changes made to the sketch, save the sketch to the graphics overlay, discard the current sketch, or clear the graphics overlay.
How it works
- Create a
SketchEditorcomponent and set it on the map view withMapView::setSketchEditor. - Call
SketchEditor::startand pass in aSketchCreationModeenum to begin sketching. - While sketching is in progress, call
SketchEditor::undo()orSketchEditor::redo()to undo or redo edits respectively. - To save the sketch, first check if the sketch is valid with
SketchEditor::isSketchValid(). If it is, create a graphic usingSketchEditor::geometry()and add it to the map view’sGraphicsOverlay. - Call
SketchEditor::stop()to stop sketching.
Relevant API
- Geometry
- Graphic
- GraphicsOverlay
- MapView
- SketchCreationMode
- SketchEditor
Tags
draw, edit
Sample Code
// [WriteFile Name=SketchOnMap, Category=DisplayInformation]// [Legal]// Copyright 2021 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.Layouts
// This component defines each of the buttons in the sketch editor control UI
RoundButton { id: sketchEditorButton
property var columnSpan: 1 property string buttonName: "" property string iconPath: "" property var images: 1
Layout.fillWidth: true Layout.columnSpan: columnSpan radius: 5
// Set the focus policy so that the buttons do not take focus from the MapView focusPolicy: Qt.NoFocus
Image { id: imgComponent anchors { horizontalCenter: parent.horizontalCenter verticalCenter: parent.verticalCenter verticalCenterOffset: -textComponent.height/2 } source: iconPath width: 20 fillMode: Image.PreserveAspectFit visible: images === 1 }
Image { id: imgComponentLeft anchors { right: parent.horizontalCenter rightMargin: -imgComponentRight.width/5 verticalCenter: parent.verticalCenter verticalCenterOffset: -textComponent.height/2 - 2 } source: iconPath width: 20 fillMode: Image.PreserveAspectFit
visible: images === 2 }
Image { id: imgComponentRight anchors { left: parent.horizontalCenter leftMargin: -imgComponentLeft.width/5 verticalCenter: parent.verticalCenter verticalCenterOffset: -textComponent.height/2 } source: iconPath width: 20 fillMode: Image.PreserveAspectFit
visible: images === 2 }
Text { id: textComponent anchors { top: images === 1 ? imgComponent.bottom : imgComponentRight.bottom horizontalCenter: parent.horizontalCenter } text: buttonName font.pixelSize: 8 }}// [WriteFile Name=SketchOnMap, Category=DisplayInformation]// [Legal]// Copyright 2021 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 "SketchOnMap.h"
// ArcGIS Maps SDK headers#include "Envelope.h"#include "Graphic.h"#include "GraphicListModel.h"#include "GraphicsOverlay.h"#include "GraphicsOverlayListModel.h"#include "Map.h"#include "MapQuickView.h"#include "MapTypes.h"#include "MapViewTypes.h"#include "Point.h"#include "SimpleFillSymbol.h"#include "SimpleLineSymbol.h"#include "SimpleMarkerSymbol.h"#include "SketchEditor.h"#include "SpatialReference.h"#include "SymbolTypes.h"
// Qt headers#include <QFuture>
using namespace Esri::ArcGISRuntime;
SketchOnMap::SketchOnMap(QObject* parent /* = nullptr */): QObject(parent), m_map(new Map(BasemapStyle::ArcGISImagery, this)){ // Create a GraphicsOverlay to save the sketches to m_sketchOverlay = new GraphicsOverlay(this); createSymbols();}
SketchOnMap::~SketchOnMap() = default;
void SketchOnMap::init(){ // Register the map view for QML qmlRegisterType<MapQuickView>("Esri.Samples", 1, 0, "MapView"); qmlRegisterType<SketchOnMap>("Esri.Samples", 1, 0, "SketchOnMapSample");}
MapQuickView* SketchOnMap::mapView() const{ return m_mapView;}
// Set the view (created in QML)void SketchOnMap::setMapView(MapQuickView* mapView){ if (!mapView || mapView == m_mapView) return;
m_mapView = mapView; m_mapView->setMap(m_map); m_mapView->setViewpointCenterAsync(Point(-15.5314, 64.3286, SpatialReference::wgs84()), 100'000);
m_mapView->graphicsOverlays()->append(m_sketchOverlay);
m_sketchEditor = new SketchEditor(this); m_mapView->setSketchEditor(m_sketchEditor);
emit mapViewChanged();}
void SketchOnMap::setSketchCreationMode(SampleSketchMode sketchCreationMode){ switch(sketchCreationMode) { case SampleSketchMode::PointSketchMode: m_sketchEditor->start(SketchCreationMode::Point); break;
case SampleSketchMode::MultipointSketchMode: m_sketchEditor->start(SketchCreationMode::Multipoint); break;
case SampleSketchMode::PolylineSketchMode: m_sketchEditor->start(SketchCreationMode::Polyline); break;
case SampleSketchMode::PolygonSketchMode: m_sketchEditor->start(SketchCreationMode::Polygon); break;
default: break; } emit sketchEditorStartedChanged();}
void SketchOnMap::stopSketching(bool saveGeometry){ if (!m_sketchEditor->isStarted() || !saveGeometry) { m_sketchEditor->stop(); emit sketchEditorStartedChanged(); return; }
if (!m_sketchEditor->isSketchValid()) { qWarning() << "Unable to save sketch. Sketch is not valid."; return; }
// To save the sketch, create a graphic with the sketch's geometry before stopping the sketchEditor Geometry sketchGeometry = m_sketchEditor->geometry(); Symbol* geometrySymbol = nullptr;
switch (m_sketchEditor->creationMode()) { case SketchCreationMode::Point: geometrySymbol = m_pointSymbol; break;
case SketchCreationMode::Multipoint: geometrySymbol = m_multiPointSymbol; break;
case SketchCreationMode::Polyline: geometrySymbol = m_lineSymbol; break;
case SketchCreationMode::Polygon: geometrySymbol = m_polygonSymbol; break;
default: return; }
Graphic* sketchGraphic = new Graphic(sketchGeometry, geometrySymbol, this); m_sketchOverlay->graphics()->append(sketchGraphic);
m_sketchEditor->stop(); emit sketchEditorStartedChanged();}
bool SketchOnMap::sketchEditorStarted() const{ if (m_sketchEditor) return m_sketchEditor->isStarted();
return false;}
void SketchOnMap::createSymbols(){ m_pointSymbol = new SimpleMarkerSymbol(SimpleMarkerSymbolStyle::Square, QColor(255, 0, 0), 10, this); m_multiPointSymbol = new SimpleMarkerSymbol(SimpleMarkerSymbolStyle::Square, QColor(0, 0, 255), 10, this); m_lineSymbol = new SimpleLineSymbol(SimpleLineSymbolStyle::Solid, QColor(144, 238, 144), 3, this); m_polygonSymbol = new SimpleFillSymbol(SimpleFillSymbolStyle::Solid, QColor(67, 166, 198, 119), new SimpleLineSymbol(SimpleLineSymbolStyle::Solid, QColor(67, 166, 198), 2.0, this), this);}
void SketchOnMap::deleteVertex(){ if (m_sketchEditor->geometry().extent().isValid()) m_sketchEditor->removeSelectedVertex();}
void SketchOnMap::clearGraphics(){ m_sketchOverlay->graphics()->clear();}
void SketchOnMap::undo(){ m_sketchEditor->undo();}
void SketchOnMap::redo(){ m_sketchEditor->redo();}// [WriteFile Name=SketchOnMap, Category=DisplayInformation]// [Legal]// Copyright 2021 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 SKETCHONMAP_H#define SKETCHONMAP_H
// Qt headers#include <QObject>
namespace Esri::ArcGISRuntime{class Map;class MapQuickView;class Graphic;class GraphicsOverlay;class SketchEditor;class SimpleMarkerSymbol;class SimpleLineSymbol;class SimpleFillSymbol;}
Q_MOC_INCLUDE("MapQuickView.h")
class SketchOnMap : public QObject{ Q_OBJECT
Q_PROPERTY(Esri::ArcGISRuntime::MapQuickView* mapView READ mapView WRITE setMapView NOTIFY mapViewChanged) Q_PROPERTY(bool sketchEditorStarted READ sketchEditorStarted NOTIFY sketchEditorStartedChanged)
public: explicit SketchOnMap(QObject* parent = nullptr); ~SketchOnMap();
enum class SampleSketchMode { PointSketchMode, MultipointSketchMode, PolylineSketchMode, PolygonSketchMode };
Q_ENUM(SampleSketchMode)
static void init(); Q_INVOKABLE void setSketchCreationMode(SketchOnMap::SampleSketchMode sketchCreationMode); Q_INVOKABLE void stopSketching(bool saveGeometry); Q_INVOKABLE void clearGraphics(); Q_INVOKABLE void undo(); Q_INVOKABLE void redo(); Q_INVOKABLE void deleteVertex();
signals: void mapViewChanged(); void sketchEditorStartedChanged();
private: Esri::ArcGISRuntime::MapQuickView* mapView() const; void setMapView(Esri::ArcGISRuntime::MapQuickView* mapView); void createConnections(); void createSymbols(); bool sketchEditorStarted() const;
Esri::ArcGISRuntime::Map* m_map = nullptr; Esri::ArcGISRuntime::MapQuickView* m_mapView = nullptr; Esri::ArcGISRuntime::GraphicsOverlay* m_sketchOverlay = nullptr; Esri::ArcGISRuntime::SketchEditor* m_sketchEditor = nullptr; Esri::ArcGISRuntime::Graphic* editingGraphic = nullptr;
Esri::ArcGISRuntime::SimpleMarkerSymbol* m_pointSymbol = nullptr; Esri::ArcGISRuntime::SimpleMarkerSymbol* m_multiPointSymbol = nullptr; Esri::ArcGISRuntime::SimpleLineSymbol* m_lineSymbol = nullptr; Esri::ArcGISRuntime::SimpleFillSymbol* m_polygonSymbol = nullptr;};
#endif // SKETCHONMAP_H// [WriteFile Name=SketchOnMap, Category=DisplayInformation]// [Legal]// Copyright 2021 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.Layoutsimport Esri.Samples
Item {
enum DrawingModes { NotDrawing, Point, Multipoint, Line, Polygon }
property int drawStatus: SketchOnMap.DrawingModes.NotDrawing
SketchOnMapSample { id: model mapView: view }
MapView { id: view anchors.fill: parent
// Force focus to remain on MapView so SketchEditor will respond to keystrokes onFocusChanged: focus = true;
Component.onCompleted: { // Set the focus on MapView to initially enable keyboard navigation forceActiveFocus(); } }
// Display an option to delete the selected vertex if the user right-clicks or taps and holds when the sketch editor is started MouseArea { anchors.fill: parent acceptedButtons: Qt.RightButton
enabled: model.sketchEditorStarted onClicked: { contextMenu.popup() }
Menu { id: contextMenu width: actionComponent.width Action { id: actionComponent text: "Delete" onTriggered: model.deleteVertex(); } } }
Control { id: control anchors.right: parent.right padding: 5 width: 110
background: Rectangle { color: "black" opacity: .5 }
contentItem: ColumnLayout { id: columns anchors { verticalCenter: parent.verticalCenter horizontalCenter: parent.horizontalCenter } spacing: 20
GridLayout { id: geometryColumn Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter columns: 2
Text { id: geometryHeader Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter Layout.columnSpan: 2 text: "Geometry" color: "white" font.pixelSize: 16 font.bold: true }
SketchEditorButton { id: ptButton buttonName: "Point" iconPath: "qrc:/Samples/DisplayInformation/SketchOnMap/iconAssets/point-32.png"
highlighted: drawStatus === SketchOnMap.DrawingModes.Point enabled: !model.sketchEditorStarted
onClicked: { model.setSketchCreationMode(SketchOnMapSample.PointSketchMode); drawStatus = SketchOnMap.DrawingModes.Point; } }
SketchEditorButton { id: mPtButton buttonName: "Multipoint" iconPath: "qrc:/Samples/DisplayInformation/SketchOnMap/iconAssets/point-32.png" images: 2
highlighted: drawStatus === SketchOnMap.DrawingModes.Multipoint enabled: !model.sketchEditorStarted
onClicked: { model.setSketchCreationMode(SketchOnMapSample.MultipointSketchMode); drawStatus = SketchOnMap.DrawingModes.Multipoint; } }
SketchEditorButton { id: lineButton buttonName: "Line" iconPath: "qrc:/Samples/DisplayInformation/SketchOnMap/iconAssets/line-32.png"
highlighted: drawStatus === SketchOnMap.DrawingModes.Line enabled: !model.sketchEditorStarted
onClicked: { model.setSketchCreationMode(SketchOnMapSample.PolylineSketchMode); drawStatus = SketchOnMap.DrawingModes.Line; } }
SketchEditorButton { id: polygonButton buttonName: "Polygon" iconPath: "qrc:/Samples/DisplayInformation/SketchOnMap/iconAssets/polygon-32.png"
highlighted: drawStatus === SketchOnMap.DrawingModes.Polygon enabled: !model.sketchEditorStarted
onClicked: { model.setSketchCreationMode(SketchOnMapSample.PolygonSketchMode); drawStatus = SketchOnMap.DrawingModes.Polygon; } } }
GridLayout { id: editingColumn Layout.fillWidth: true Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter columns: 2
Text { id: editingHeader Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter Layout.columnSpan: 2 text: "Editing" color: "white" font.pixelSize: 16 font.bold: true }
SketchEditorButton { id: undoButton buttonName: "Undo" iconPath: "qrc:/Samples/DisplayInformation/SketchOnMap/iconAssets/undo-32.png"
enabled: model.sketchEditorStarted
onClicked: model.undo(); }
SketchEditorButton { id: redoButton buttonName: "Redo" iconPath: "qrc:/Samples/DisplayInformation/SketchOnMap/iconAssets/redo-32.png"
enabled: model.sketchEditorStarted
onClicked: model.redo(); }
SketchEditorButton { id: deleteSelectedVertexButton columnSpan: 2 buttonName: "Delete selected vertex" iconPath: "qrc:/Samples/DisplayInformation/SketchOnMap/iconAssets/erase-32.png"
enabled: model.sketchEditorStarted
onClicked: model.deleteVertex(); }
SketchEditorButton { id: saveEditsButton columnSpan: 2 buttonName: "Save edits" iconPath: "qrc:/Samples/DisplayInformation/SketchOnMap/iconAssets/check-circle-32.png"
enabled: model.sketchEditorStarted
onClicked: { model.stopSketching(true); drawStatus = SketchOnMap.DrawingModes.NotDrawing if (!model.sketchEditorStarted && !clearGraphicsButton.enabled) clearGraphicsButton.enabled = true; } }
SketchEditorButton { id: discardEditsButton columnSpan: 2 buttonName: "Discard edits" iconPath: "qrc:/Samples/DisplayInformation/SketchOnMap/iconAssets/circle-disallowed-32.png"
enabled: model.sketchEditorStarted
onClicked: { model.stopSketching(false); drawStatus = SketchOnMap.DrawingModes.NotDrawing } }
SketchEditorButton { id: clearGraphicsButton columnSpan: 2 buttonName: "Clear graphics" iconPath: "qrc:/Samples/DisplayInformation/SketchOnMap/iconAssets/trash-32.png"
enabled: false
onClicked: { model.clearGraphics(); enabled = false; } } } } }}// [Legal]// Copyright 2021 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]
// sample headers#include "SketchOnMap.h"
// ArcGIS Maps SDK headers#include "ArcGISRuntimeEnvironment.h"
// Qt headers#include <QDir>#include <QGuiApplication>#include <QQmlApplicationEngine>
// Platform specific headers#ifdef Q_OS_WIN#include <Windows.h>#endif
int main(int argc, char *argv[]){ Esri::ArcGISRuntime::ArcGISRuntimeEnvironment::setUseLegacyAuthentication(false); QGuiApplication app(argc, argv); app.setApplicationName(QString("SketchOnMap"));
// Use of ArcGIS location services, such as basemap styles, geocoding, and routing services, // requires an access token. For more information see // https://links.esri.com/arcgis-runtime-security-auth.
// The following methods grant an access token:
// 1. User authentication: Grants a temporary access token associated with a user's ArcGIS account. // To generate a token, a user logs in to the app with an ArcGIS account that is part of an // organization in ArcGIS Online or ArcGIS Enterprise.
// 2. API key authentication: Get a long-lived access token that gives your application access to // ArcGIS location services. Go to the tutorial at https://links.esri.com/create-an-api-key. // Copy the API Key access token.
const QString accessToken = QString("");
if (accessToken.isEmpty()) { qWarning() << "Use of ArcGIS location services, such as the basemap styles service, requires" << "you to authenticate with an ArcGIS account or set the API Key property."; } else { Esri::ArcGISRuntime::ArcGISRuntimeEnvironment::setApiKey(accessToken); }
// Initialize the sample SketchOnMap::init();
// Initialize application view QQmlApplicationEngine engine; // Add the import Path engine.addImportPath(QDir(QCoreApplication::applicationDirPath()).filePath("qml"));
#ifdef ARCGIS_RUNTIME_IMPORT_PATH_2 engine.addImportPath(ARCGIS_RUNTIME_IMPORT_PATH_2);#endif
// Set the source engine.load(QUrl("qrc:/Samples/DisplayInformation/SketchOnMap/main.qml"));
return app.exec();}// Copyright 2021 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.
import QtQuick.Controlsimport Esri.Samples
ApplicationWindow { visible: true width: 800 height: 600
SketchOnMap { anchors.fill: parent }}