Create and share a mobile geodatabase.

Use case
A mobile geodatabase is a collection of various types of GIS datasets contained in a single file (.geodatabase) on disk that can store, query, and manage spatial and nonspatial data. Mobile geodatabases are stored in a SQLite database and can contain up to 2 TB of portable data. Users can create, edit and share mobile geodatabases across ArcGIS Pro, ArcGIS Maps SDK for Native Apps, or any SQL software. These mobile geodatabases support both viewing and editing and enable new offline editing workflows that don’t require a feature service.
For example, a user would like to track the location of their device at various intervals to generate a heat map of the most visited locations. The user can add each location as a feature to a table and generate a mobile geodatabase. The user can then instantly share the mobile geodatabase to ArcGIS Pro to generate a heat map using the recorded locations stored as a geodatabase feature table.
How to use the sample
Select “Create new .geodatabase” to create a new mobile geodatabase in a temporary directory. Click or tap on the map to add features to the mobile geodatabase. Select “View feature table” to view the contents of the geodatabase feature table. Select “Clear features” to remove all features from the geodatabase feature table. Select “Close .geodatabase” to save and close the geodatabase so it is ready to be shared or uploaded to ArcGIS Online.
How it works
- Create a new
Geodatabaseat a given path that does not already exist with theGeodatabase::createAsync()static method. - Create a
TableDescriptionand add a list ofFieldDescriptions to the table description. - Create a
GeodatabaseFeatureTablefrom the geodatabase with theTableDescriptionusingGeodatabase::createTableAsync(TableDescription* tableDescription). - Create an
ArcGISFeatureon a selected map point usingFeatureTable::createFeature(const QVariantMap &attributes, const Geometry& geometry, QObject* parent = nullptr). - Add the feature to the table using
FeatureTable::addFeatureAsync(Feature* feature) - Each feature added to the feature table is committed to the mobile geodatabase file.
- Close the mobile geodatabase to safely share the “.geodatabase” file using
Geodatabase::close()
Relevant API
- ArcGISFeature
- FeatureLayer
- FeatureTable
- FieldDescription
- Geodatabase
- GeodatabaseFeatureTable
- TableDescription
Additional information
Learn more about mobile geodatabases and how to utilize them on the Mobile geodatabases page. The following mobile geodatabase behaviors are supported in ArcGIS Maps SDK for Native Apps: annotation, attachments, attribute rules, contingent values, dimensions, domains, feature-linked annotation, subtypes, utility network and relationship classes.
Visit ArcGIS field data types to learn more about the types of fields supported.
Tags
arcgis pro, database, feature, feature table, geodatabase, mobile geodatabase, sqlite
Sample Code
// [WriteFile Name=CreateMobileGeodatabase, Category=Features]// [Legal]// Copyright 2022 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 "CreateMobileGeodatabase.h"#include "FeatureListModel.h"
// ArcGIS Maps SDK headers#include "FeatureIterator.h"#include "FeatureLayer.h"#include "FeatureQueryResult.h"#include "FieldDescription.h"#include "FieldDescriptionListModel.h"#include "Geodatabase.h"#include "GeodatabaseFeatureTable.h"#include "GeometryTypes.h"#include "LayerListModel.h"#include "Map.h"#include "MapQuickView.h"#include "MapTypes.h"#include "Point.h"#include "QueryParameters.h"#include "ServiceTypes.h"#include "SpatialReference.h"#include "TableDescription.h"#include "Viewpoint.h"
// Qt headers#include <QFuture>
using namespace Esri::ArcGISRuntime;
CreateMobileGeodatabase::CreateMobileGeodatabase(QObject* parent /* = nullptr */): QObject(parent), m_map(new Map(BasemapStyle::ArcGISTopographic, this)){ // The FeatureListModel is a helper class created specifically for this sample to display all features in the table view m_featureListModel = new FeatureListModel(this);}
CreateMobileGeodatabase::~CreateMobileGeodatabase() = default;
void CreateMobileGeodatabase::init(){ // Register the map view for QML qmlRegisterType<MapQuickView>("Esri.Samples", 1, 0, "MapView"); qmlRegisterType<CreateMobileGeodatabase>("Esri.Samples", 1, 0, "CreateMobileGeodatabaseSample");}
MapQuickView* CreateMobileGeodatabase::mapView() const{ return m_mapView;}
// Set the view (created in QML)void CreateMobileGeodatabase::setMapView(MapQuickView* mapView){ if (!mapView || mapView == m_mapView) return;
m_mapView = mapView; m_mapView->setMap(m_map); m_mapView->setViewpointAsync(Viewpoint(39.3238, -77.7332, 10'000));
connect(m_mapView, &MapQuickView::mouseClicked, this, &CreateMobileGeodatabase::addFeature);
emit mapViewChanged();}
// Create a Geodatabase in an empty directoryvoid CreateMobileGeodatabase::createGeodatabase(){ m_gdbFilePath = QString{m_tempDir.path() + "/LocationHistory_%1.geodatabase"}.arg(QDateTime::currentSecsSinceEpoch() % 1000); emit gdbFilePathChanged();
// We cannot overwrite an existing geodatabase if (QFile::exists(m_gdbFilePath)) return;
// Use static Geodatabase::createAsync with an empty file path Geodatabase::createAsync(m_gdbFilePath, this).then(this, [this](Geodatabase* geodatabase) { m_gdb = geodatabase; createTable(); });}
// Create a GeodatabaseFeatureTable from the new Geodatabase with a TableDescriptionvoid CreateMobileGeodatabase::createTable(){ m_gdbOpen = true; emit gdbOpenChanged();
// Create a TableDescription to define the GeodatabaseFeatureTable's attributes and fields TableDescription* tableDescription = new TableDescription("LocationHistory", SpatialReference::wgs84(), GeometryType::Point, this); tableDescription->setHasAttachments(false); tableDescription->setHasM(false); tableDescription->setHasZ(false); tableDescription->fieldDescriptions()->append(new FieldDescription("oid", FieldType::OID)); tableDescription->fieldDescriptions()->append(new FieldDescription("collection_timestamp", FieldType::Date));
m_gdb->createTableAsync(tableDescription, this).then(this, [this](GeodatabaseFeatureTable* gdbFeatureTableResult) { m_featureTable = gdbFeatureTableResult;
FeatureLayer* featureLayer = new FeatureLayer(m_featureTable, this); m_map->operationalLayers()->append(featureLayer); });}
// Close the Geodatabase so that it can be safely sharedvoid CreateMobileGeodatabase::closeGdb(){ if (!m_gdb) return;
m_gdb->close(); m_gdbOpen = false;
m_map->operationalLayers()->clear(); m_featureListModel->clear();
m_featureCount = 0;
emit gdbOpenChanged(); emit featureCountChanged();}
void CreateMobileGeodatabase::addFeature(QMouseEvent& mouseEvent){ if (!m_featureTable) return;
const Point mousePoint = m_mapView->screenToLocation(mouseEvent.position().x(), mouseEvent.position().y()); QVariantMap attributes = {}; attributes.insert("collection_timestamp", QDateTime::currentDateTime()); Feature* feature = m_featureTable->createFeature(attributes, mousePoint, this);
m_featureTable->addFeatureAsync(feature).then(this, [this, feature]() { m_featureListModel->addFeature(feature); emit featureListModelChanged();
m_featureCount = static_cast<int>(m_featureTable->numberOfFeatures()); emit featureCountChanged(); });}
void CreateMobileGeodatabase::deleteFeatures(){ QueryParameters params; params.setWhereClause("1=1");
m_featureTable->queryFeaturesAsync(params).then(this, [this](FeatureQueryResult* rawQueryResult) { // Cast the FeatureQueryResult to a unique pointer to delete it when it goes out of scope auto queryResults = std::unique_ptr<FeatureQueryResult>(rawQueryResult); FeatureIterator resultIterator = queryResults->iterator();
auto deleteFeaturesFuture = m_featureTable->deleteFeaturesAsync(resultIterator.features()); Q_UNUSED(deleteFeaturesFuture) m_featureCount = 0; emit featureCountChanged(); });}
void CreateMobileGeodatabase::clearFeatures(){ if (m_featureTable->numberOfFeatures() > 0) deleteFeatures();
m_featureListModel->clear();}
FeatureListModel* CreateMobileGeodatabase::featureListModel() const{ return m_featureListModel;}
QString CreateMobileGeodatabase::gdbFilePath() const{ return m_gdbFilePath;}
int CreateMobileGeodatabase::featureCount() const{ return m_featureCount;}
bool CreateMobileGeodatabase::gdbOpen() const{ return m_gdbOpen;}// [WriteFile Name=CreateMobileGeodatabase, Category=Features]// [Legal]// Copyright 2022 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 CREATEMOBILEGEODATABASE_H#define CREATEMOBILEGEODATABASE_H
// sample headers#include "FeatureListModel.h"
// Qt headers#include <QMouseEvent>#include <QObject>#include <QTemporaryDir>
namespace Esri::ArcGISRuntime{class FeatureTable;class Geodatabase;class Map;class MapQuickView;}
Q_MOC_INCLUDE("MapQuickView.h")
class CreateMobileGeodatabase : public QObject{ Q_OBJECT
Q_PROPERTY(Esri::ArcGISRuntime::MapQuickView* mapView READ mapView WRITE setMapView NOTIFY mapViewChanged) Q_PROPERTY(FeatureListModel* featureListModel READ featureListModel NOTIFY featureListModelChanged) Q_PROPERTY(QString gdbFilePath READ gdbFilePath NOTIFY gdbFilePathChanged) Q_PROPERTY(bool gdbOpen READ gdbOpen NOTIFY gdbOpenChanged) Q_PROPERTY(int featureCount READ featureCount NOTIFY featureCountChanged)
public: explicit CreateMobileGeodatabase(QObject* parent = nullptr); ~CreateMobileGeodatabase();
static void init();
Q_INVOKABLE void createGeodatabase(); Q_INVOKABLE void clearFeatures(); Q_INVOKABLE void closeGdb();
signals: void mapViewChanged(); void featureListModelChanged(); void gdbFilePathChanged(); void gdbOpenChanged(); void featureCountChanged();
private: Esri::ArcGISRuntime::MapQuickView* mapView() const; FeatureListModel* featureListModel() const; QString gdbFilePath() const; int featureCount() const; bool gdbOpen() const; void setMapView(Esri::ArcGISRuntime::MapQuickView* mapView); void createTable(); void addFeature(QMouseEvent& mouseEvent); void deleteFeatures();
Esri::ArcGISRuntime::Map* m_map = nullptr; Esri::ArcGISRuntime::MapQuickView* m_mapView = nullptr; Esri::ArcGISRuntime::FeatureTable* m_featureTable = nullptr; Esri::ArcGISRuntime::Geodatabase* m_gdb = nullptr; FeatureListModel* m_featureListModel = nullptr; QString m_gdbFilePath; bool m_gdbOpen = false; int m_featureCount = 0; QTemporaryDir m_tempDir;};
#endif // CREATEMOBILEGEODATABASE_H// [WriteFile Name=CreateMobileGeodatabase, Category=Features]// [Legal]// Copyright 2022 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 Qt.labs.platformimport Esri.Samples
Item { id: rootItem // add a mapView component MapView { id: view anchors.fill: parent
Component.onCompleted: { // Set and keep the focus on MapView to enable keyboard navigation forceActiveFocus(); } }
CreateMobileGeodatabaseSample { id: model mapView: view }
Rectangle { id: buttonListRectangle anchors { top: parent.top right: parent.right topMargin: 10 rightMargin: 10 }
width: 250 height: buttonColumn.height + 20 color: "#ffffff"
// Prevent mouse interaction from propagating to the MapView MouseArea { anchors.fill: parent onPressed: mouse => mouse.accepted = true; onWheel: wheel => wheel.accepted = true; }
Column { id: buttonColumn anchors { top: parent.top left: parent.left right: parent.right topMargin: 10 leftMargin: 10 rightMargin: 10 } height: children.height spacing: 5
onHeightChanged: { buttonListRectangle.height = height + 20 }
Button { id: createGdbButton anchors { left: parent.left right: parent.right leftMargin: 0 rightMargin: 0 } text: qsTr("Create new .geodatabase") enabled: !model.gdbOpen onClicked: { model.createGeodatabase(); } }
Button { id: viewGdbTableButton anchors { left: parent.left right: parent.right leftMargin: 0 rightMargin: 0 } text: qsTr("View feature table") enabled: model.gdbOpen onClicked: { featureTableDisplay.visible = true; buttonListRectangle.visible = false; } } Button { id: clearFeaturesButton anchors { left: parent.left right: parent.right leftMargin: 0 rightMargin: 0 } text: qsTr("Clear features") enabled: model.gdbOpen onClicked: { model.clearFeatures(); } }
Button { id: closeGdbButton anchors { left: parent.left right: parent.right leftMargin: 0 rightMargin: 0 } text: qsTr("Close .geodatabase") enabled: model.gdbOpen onClicked: { model.closeGdb(); gdbClosedNoticeRectangle.visible = true; buttonListRectangle.visible = false; } }
Text { id: fileNameText anchors { left: parent.left right: parent.right leftMargin: 0 rightMargin: 0 } text: model.gdbFilePath ? "Created new geodatabase:\n" + model.gdbFilePath.split("/").pop() : "Geodatabase path not found" font.pixelSize: 12 wrapMode: Text.WrapAnywhere visible: model.gdbOpen }
Text { id: featureCountText anchors { left: parent.left right: parent.right leftMargin: 0 rightMargin: 0 } text: "Number of features: " + model.featureCount + (model.featureCount === 0 ? "\n(Click or tap on the map to add new features)" : "") font.pixelSize: 12 wrapMode: Text.WordWrap visible: model.gdbOpen } } }
Rectangle { id: gdbClosedNoticeRectangle anchors.centerIn: parent width: parent.width * 0.75 height: gdbInfoColumn.height + 20 color: "white" border.color: "black" clip: true visible: false
// Prevent mouse interaction from propagating to the MapView MouseArea { anchors.fill: parent onPressed: mouse => mouse.accepted = true; onWheel: wheel => wheel.accepted = true; }
Column { id: gdbInfoColumn anchors { centerIn: parent margins: 10 } spacing: 10 width: parent.width - 20 height: children.height
Text { id: gdbNameText width: parent.width text: "Closed and saved geodatabase to the temporary path:" wrapMode: Text.WordWrap } TextEdit { id: gdbPathText width: parent.width readOnly: true activeFocusOnPress: false selectByMouse: true text: model.gdbFilePath wrapMode: Text.WrapAnywhere } Button { id: gdbInfoClose anchors.horizontalCenter: parent.horizontalCenter text: "Ok" onClicked: { gdbClosedNoticeRectangle.visible = false; buttonListRectangle.visible = true; } } } }
Rectangle { id: featureTableDisplay anchors { verticalCenter: parent.verticalCenter horizontalCenter: parent.horizontalCenter } width: parent.width - parent.width * 0.2 height: parent.height - parent.height * 0.3 color: "#80808080" visible: false
// Prevent mouse interaction from propagating to the MapView MouseArea { anchors.fill: parent onPressed: mouse => mouse.accepted = true; onWheel: wheel => wheel.accepted = true; }
ListView { id: tableView anchors { fill: parent margins: 10 } ScrollBar.vertical: ScrollBar { active: true } clip: true
header: Row { height: 40 width: tableView.width Rectangle { color: "grey" border.color: "black" width: parent.width * 0.2 height: parent.height Text { anchors.centerIn: parent text: "Object ID" color: "white" font.bold: true
} } Rectangle { color: "grey" border.color: "black" width: parent.width * 0.8 height: parent.height Text { anchors.centerIn: parent text: "Timestamp" color: "white" font.bold: true } } }
model: model.featureListModel delegate: Row { height: 40 width: tableView.width Rectangle { color: "white" border.color: "black" width: parent.width * 0.2 height: parent.height Text { anchors.centerIn: parent text: featureOidRole
} } Rectangle { color: "white" border.color: "black" width: parent.width * 0.8 height: parent.height Text { anchors.centerIn: parent text: featureTimestampRole } } } } }
Rectangle { id: closeTableButtonRectangle anchors { top: parent.top right: parent.right topMargin: 10 rightMargin: 10 } width: closeTableButton.width + 10 height: closeTableButton.height + 10 color: "#ffffff" visible: featureTableDisplay.visible
// Prevent mouse interaction from propagating to the MapView MouseArea { anchors.fill: parent onPressed: mouse => mouse.accepted = true; onWheel: wheel => wheel.accepted = true; }
Button { id: closeTableButton anchors.centerIn: parent
text: "Close table view"
onClicked: { featureTableDisplay.visible = false; buttonListRectangle.visible = true; } } }}// [WriteFile Name=FeatureListModel, Category=Features]// [Legal]// Copyright 2016 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 "FeatureListModel.h"
// ArcGIS Maps SDK headers#include "AttributeListModel.h"#include "Feature.h"
using namespace Esri::ArcGISRuntime;
FeatureListModel::FeatureListModel(QObject* parent /* = nullptr */) : QAbstractListModel(parent){}
void FeatureListModel::addFeature(Feature* feature){ beginInsertRows(QModelIndex(), rowCount(), rowCount()); m_features << feature; endInsertRows();}
int FeatureListModel::rowCount(const QModelIndex& parent) const{ Q_UNUSED(parent); return m_features.count();}
QVariant FeatureListModel::data(const QModelIndex& index, int role) const{ if (index.row() < 0 || index.row() >= m_features.count()) return QVariant();
const Feature* feature = m_features[index.row()];
if (role == FeatureOidRole) return feature->attributes()->attributeValue("oid"); else if (role == FeatureTimestampRole) return feature->attributes()->attributeValue("collection_timestamp");
return QVariant();}
QHash<int, QByteArray> FeatureListModel::roleNames() const{ QHash<int, QByteArray> roles; roles[FeatureOidRole] = "featureOidRole"; roles[FeatureTimestampRole] = "featureTimestampRole"; return roles;}
void FeatureListModel::clear(){ beginResetModel(); m_features.clear(); endResetModel();}// [Legal]// Copyright 2024 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 FEATURELISTMODEL_H#define FEATURELISTMODEL_H
// Qt headers#include <QAbstractListModel>
namespace Esri::ArcGISRuntime{class Feature;}
class FeatureListModel : public QAbstractListModel{ Q_OBJECT
public: enum SampleFeatureRoles { FeatureOidRole, FeatureTimestampRole };
explicit FeatureListModel(QObject* parent = nullptr); ~FeatureListModel() override = default;
void addFeature(Esri::ArcGISRuntime::Feature* feature); int rowCount(const QModelIndex& parent = QModelIndex()) const override; QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; void clear(); int size() const { return m_features.size(); }
protected: QHash<int, QByteArray> roleNames() const override;
private: QList<Esri::ArcGISRuntime::Feature*> m_features;};
#endif // FEATURELISTMODEL_H// [Legal]// Copyright 2022 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 "CreateMobileGeodatabase.h"
// ArcGIS Maps SDK headers#include "ArcGISRuntimeEnvironment.h"
// Qt headers#include <QCommandLineParser>#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("CreateMobileGeodatabase"));
// 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 CreateMobileGeodatabase::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/Features/CreateMobileGeodatabase/main.qml"));
return app.exec();}// Copyright 2022 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
CreateMobileGeodatabase { anchors.fill: parent }}