Create mobile geodatabase

View on GitHub
Sample viewer app

Create and share a mobile geodatabase.

Create and share mobile geodatabase Geodatabase feature table

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 Runtime, 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

Tap on the map to add a feature symbolizing the user's location. Tap "View table" to view the contents of the geodatabase feature table. Once you have added the location points to the map, tap on "Create and share" to retrieve the .geodatabase file which can then be imported into ArcGIS Pro or opened in an ArcGIS Runtime application.

How it works

  1. Create the AGSGeodatabase from the mobile geodatabase location on file.
  2. Create a new AGSTableDescription and add the list of AGSFieldDescriptions to the table description.
  3. Create an AGSGeodatabaseFeatureTable in the geodatabase from the AGSTableDescription using AGSGeodatabase.createTable(with:completion:).
  4. Create a feature on the selected map point using AGSGeodatabaseFeatureTable.createFeature(attributes:geometry:).
  5. Add the feature to the table using AGSGeodatabaseFeatureTable.add(_:completion:).
  6. Each feature added to the AGSGeodatabaseFeatureTable is committed to the mobile geodatabase file.
  7. Close the mobile geodatabase to safely share the .geodatabase file using AGSGeodatabase.close()

Relevant API

  • AGSArcGISFeature
  • AGSFeatureLayer
  • AGSFeatureTable
  • AGSFieldDescription
  • AGSGeodatabase
  • AGSGeodatabaseFeatureTable
  • AGSTableDescription

Additional information

Learn more about mobile geodatabases and how to utilize them on the ArcGIS Pro documentation page. The following mobile geodatabase behaviors are supported in ArcGIS Runtime: annotation, attachments, attribute rules, contingent values, dimensions, domains, feature-linked annotation, subtypes, utility network and relationship classes.

Learn more about the types of fields supported with mobile geodatabases on the ArcGIS Pro documentation page.

Tags

arcgis pro, database, feature, feature table, geodatabase, mobile geodatabase, sqlite

Sample Code

CreateMobileGeodatabaseViewController.swiftCreateMobileGeodatabaseViewController.swiftMobileGeodatabaseTableViewController.swift
Use dark colors for code blocksCopy
                                                                                                                                                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
// 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 UIKit
import ArcGIS

class CreateMobileGeodatabaseViewController: UIViewController {
    @IBOutlet var mapView: AGSMapView! {
        didSet {
            // Create a map with the topographic basemap.
            mapView.map = AGSMap(basemapStyle: .arcGISTopographic)
            // Set the viewpoint.
            mapView.setViewpoint(AGSViewpoint(latitude: 39.323845, longitude: -77.733201, scale: 10_000))
            // Set the touch delegate.
            mapView.touchDelegate = self
        }
    }

    @IBOutlet var viewTableBarButtonItem: UIBarButtonItem!
    @IBOutlet var createShareBarButtonItem: UIBarButtonItem!
    @IBOutlet var featureCountLabel: UILabel!

    /// A URL to the temporary geodatabase.
    let temporaryGeodatabaseURL: URL
    /// A directory to temporarily store the geodatabase.
    let temporaryDirectory: URL
    /// The mobile geodatabase.
    var geodatabase: AGSGeodatabase?
    /// The feature table created along with the geodatabase.
    var featureTable: AGSGeodatabaseFeatureTable?

    required init?(coder: NSCoder) {
        // Create the temporary directory.
        temporaryDirectory = FileManager.default.temporaryDirectory.appendingPathComponent(ProcessInfo().globallyUniqueString)
        try? FileManager.default.createDirectory(at: temporaryDirectory, withIntermediateDirectories: false)
        // Create the geodatabase path.
        temporaryGeodatabaseURL = temporaryDirectory
            .appendingPathComponent("LocationHistory", isDirectory: false)
            .appendingPathExtension("geodatabase")
        super.init(coder: coder)
    }

    // MARK: Methods

    /// Prompt options to share the geodatabase.
    @IBAction func closeAndShare(_ sender: UIBarButtonItem) {
        // Create the activity view controller with the geodatabase URL.
        let activityViewController = UIActivityViewController(activityItems: [temporaryGeodatabaseURL], applicationActivities: nil)
        // Set the popover presentation.
        activityViewController.popoverPresentationController?.barButtonItem = sender
        // Present the activity view controller.
        present(activityViewController, animated: true)
        // Reset the map's state once the geodatabase has been shared.
        activityViewController.completionWithItemsHandler = { [weak self] _, completed, _, activityError in
            if completed {
                self?.resetMap()
            } else if let error = activityError {
                self?.presentAlert(error: error)
            }
        }
    }

    /// Query features when the "View table" button is tapped.
    @IBAction func queryFeatures() {
        guard let featureTable = featureTable else { return }
        let navigationController = storyboard!.instantiateViewController(withIdentifier: "NavigationController") as! UINavigationController
        let mobileGeodatabaseController = navigationController.viewControllers.first as! MobileGeodatabaseTableViewController
        featureTable.queryFeatures(with: AGSQueryParameters()) { [weak self] results, error in
            guard let self = self else { return }
            if let results = results {
                let features = results.featureEnumerator().allObjects
                // Create an array of each feature's OID.
                mobileGeodatabaseController.oidArray = features.compactMap { $0.attributes["oid"] as? Int }
                // Create an array of each feature's time stamps.
                mobileGeodatabaseController.collectionTimeStamps = features.compactMap { $0.attributes["collection_timestamp"] as? Date }
                self.present(navigationController, animated: true)
            } else if let error = error {
                self.presentAlert(error: error)
            }
        }
    }

    // Create the mobile geodatabase.
    func createGeodatabase() {
        // Remove the file if it already exists.
        if FileManager.default.fileExists(atPath: temporaryGeodatabaseURL.path) {
            do {
                try FileManager.default.removeItem(at: temporaryGeodatabaseURL)
            } catch {
                presentAlert(title: "File already exists")
            }
        }
        // Create the mobile geodatabase at the given URL.
        AGSGeodatabase.create(withFileURL: temporaryGeodatabaseURL) { [weak self] result, error in
            guard let self = self else { return }
            self.geodatabase = result

            // Add a new table to the geodatabase by creating one from the table description.
            self.geodatabase?.createTable(with: self.makeTableDescription()) { table, error in
                if let table = table {
                    // Load the table.
                    table.load { _ in
                        self.featureTable = table
                        // Create a feature layer using the table.
                        let featureLayer = AGSFeatureLayer(featureTable: table)
                        // Add the feature layer to the map's operational layers.
                        self.mapView.map?.operationalLayers.add(featureLayer)
                        self.featureCountLabel.text = "Number of features added: 0"
                    }
                } else if let error = error {
                    self.presentAlert(error: error)
                }
            }
        }
    }

    /// Make an `AGSTableDescription` for the geodatabase.
    func makeTableDescription() -> AGSTableDescription {
        // Create a description for the feature table.
        let tableDescription = AGSTableDescription(name: "LocationHistory", spatialReference: .wgs84(), geometryType: .point)
        // Create and add the description fields for the table.z
        // `AGSFieldType.OID` is the primary key of the SQLite table.
        // `AGSFieldType.DATE` is a date column used to store a Calendar date.
        // `AGSFieldDescription`s can be a SHORT, INTEGER, GUID, FLOAT, DOUBLE, DATE, TEXT, OID, GLOBALID, BLOB, GEOMETRY, RASTER, or XML.
        let fieldDescriptions = [
            AGSFieldDescription(name: "oid", fieldType: .OID),
            AGSFieldDescription(name: "collection_timestamp", fieldType: .date)
        ]
        tableDescription.fieldDescriptions.addObjects(from: fieldDescriptions)
        // Set any unnecessary properties to false.
        tableDescription.hasAttachments = false
        tableDescription.hasM = false
        tableDescription.hasZ = false
        return tableDescription
    }

    /// Add a feature at the provided map point.
    func addFeature(at mapPoint: AGSPoint) {
        guard let featureTable = featureTable else { return }
        // Create an attribute with the current date.
        let attributes = ["collection_timestamp": Date()]
        // Create a feature with the created attribute and geometry.
        let feature = featureTable.createFeature(attributes: attributes, geometry: mapPoint)
        // Add the feature to the feature table.
        featureTable.add(feature) { [weak self] error in
            guard let self = self else { return }
            if let error = error {
                self.presentAlert(error: error)
            } else {
                let featureCount = String(featureTable.numberOfFeatures)
                // Update the label's text to display the current number of features.
                self.featureCountLabel.text = String(format: "Number of features added: %@", featureCount)
                // Enable the view table bar button item.
                self.viewTableBarButtonItem.isEnabled = true
            }
        }
    }

    /// Remove existing operational layers and close the geodatabase.
    func resetMap() {
        if let geodatabase = geodatabase {
            // Close the geodatabase to cease all adjustments.
            geodatabase.close()
            // Remove the current feature layers.
            mapView.map?.operationalLayers.removeAllObjects()
            // Reset the button's state.
            viewTableBarButtonItem.isEnabled = false
            // Create a new mobile geodatabase.
            createGeodatabase()
        }
    }

    deinit {
        try? FileManager.default.removeItem(at: temporaryDirectory)
    }

    // MARK: UIViewController

    override func viewDidLoad() {
        super.viewDidLoad()
        // Create the initial geodatabase.
        createGeodatabase()
        // Add the source code button item to the right of navigation bar.
        (navigationItem.rightBarButtonItem as? SourceCodeBarButtonItem)?.filenames = [
            "CreateMobileGeodatabaseViewController",
            "MobileGeodatabaseTableViewController"
        ]
    }
}

// MARK: - AGSGeoViewTouchDelegate

extension CreateMobileGeodatabaseViewController: AGSGeoViewTouchDelegate {
    func geoView(_ geoView: AGSGeoView, didTapAtScreenPoint screenPoint: CGPoint, mapPoint: AGSPoint) {
        addFeature(at: mapPoint)
    }
}

Your browser is no longer supported. Please upgrade your browser for the best experience. See our browser deprecation post for more details.