Work with Geotriggers

Geotriggers let you monitor your GIS data in real-time and receive notifications when specified conditions are met. The most common type of notification is when a device (such as a cellphone) enters or leaves an area (sometimes called Geofencing). With geotriggers, you can monitor locations from whatever LocationDataSource you choose, while tapping into the full power of the GeometryEngine for evaluating spatial relationships.

With geotriggers your apps can do things like:

  • Warn users when they enter a restricted or dangerous area.
  • Provide notifications about nearby points of interest or attractions.
  • Inform drivers when they enter or leave an area that has noise or speed restrictions.
  • Notify a customer when their delivery is about to arrive.
A geotrigger that routes to an associated charging station.

Using geotriggers in your app involves the following main steps.

  1. Define a geotrigger condition to monitor.
  2. Set up a geotrigger monitor and listen for events.
  3. React to notification events.

Define a Geotrigger condition to monitor

A Geotrigger defines a condition that you want to monitor as the device location changes. Use the FenceGeotrigger class to monitor conditions based on a spatial relationship.

For example, you could use a fence geotrigger to represent a condition such as "Inform me when my device location comes within 50 meters of one of my target areas".

The table below shows the meaningful parts of this condition and how they are represented in the Geotrigger API:

PartExampleAPI
Dynamic position"my device location"GeotriggerFeed
Spatial operation"comes within"FenceRuleType
Areas (fences) to check"my target areas"FenceParameters

Geotrigger feed

To monitor your device location, pass a LocationDataSource to a LocationGeotriggerFeed. The LocationDataSource is also what you use with the LocationDisplay to show your position on a map.

You can use the following types of LocationDataSource depending on your use case.

The following example creates a simulated location data source that follows a provided route polyline. You can also configure a simulated location data source with a velocity (in meters per second) and the time of day (useful when considering traffic conditions, for example).

The location data source is used to configure a Geotrigger feed as well as the map view's location display.

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
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
    // Create a new simulated location data source.
    SimulatedLocationDataSource* simulatedDeviceLocation = new SimulatedLocationDataSource(this);

    // Create a new simulation parameters; set the start time and velocity.
    SimulationParameters* simulatedLocationDataSource = new SimulationParameters(this);
    simulatedLocationDataSource->setStartTime(QDateTime::currentDateTime());
    simulatedLocationDataSource->setVelocity(100.0); // Meters/Second

    // The simulated location will move across the provided polyline.
    simulatedDeviceLocation->setLocationsWithPolyline(routeLine, simulatedLocationDataSource);
    // ...

    // Create a new Geotrigger feed with the simulated location source.
    LocationGeotriggerFeed* locationGeotriggerFeed = new LocationGeotriggerFeed(simulatedDeviceLocation, this);
    // ...

    // Get the location display from the map view.
    LocationDisplay* locationDisplay = m_mapView->locationDisplay();

    // Enable location display on the map view using the same simulated location source.
    locationDisplay->setDataSource(simulatedDeviceLocation);
    locationDisplay->start();

Fence parameters

You supply a FenceParameters to monitor target areas, including an optional buffer distance. Depending on your workflow, you can make your fences visible or hidden in the map. Your user may not need to know exactly where the fences are, only when one has been entered or exited.

Your fences are based on either:

  • Graphics, which are light-weight, in-memory, and quick to create without needing a defined table schema. For example, graphics could represent a target area that the user sketches on the view. Graphic fences are dynamic, with new ones added, old ones removed, or geometry updates processed on-the-fly. For example, if your fences are polygons representing moving storm clouds, changes to these graphics will be evaluated in real-time. Use graphics when your condition uses temporary data created on the device. To represent fences using graphics, create a GraphicFenceParameters or a GraphicsOverlayFenceParameters.

  • Features from a FeatureTable that can be filtered by attributes and/or geometry. Features allow you to monitor online and offline data that can be shared across the Esri ecosystem. Use features when your condition makes use of authoritative data that you share with your map or scene. You create a FeatureFenceParameters to work with features.

The following example creates a fence geotrigger using the simulated location feed set up previously. The fence features come from a feature table of service areas defined by drive time with a buffer of 50 meters applied. Specifying a rule type of "enter" means notifications will be triggered when the fences are entered, but not when exited.

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
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
    // Create parameters that define the fence features and a buffer distance (meters).
    FeatureFenceParameters* featureFenceParameters = new FeatureFenceParameters(driveTimeServiceAreas->featureTable(), 50, this);

    // Create a geotrigger with the location feed, "enter" rule type, and the fence parameters.
    FenceGeotrigger* fenceGeotrigger = new FenceGeotrigger(locationFeed, FenceRuleType::Enter, featureFenceParameters, this);

Set up a Geotrigger monitor and listen for events

Once you have defined your geotrigger, you are ready to start monitoring it. The geotrigger itself just defines what you want to be notified about. To actually check for when things happen (conditions are met), you pass it to a geotrigger monitor.

The GeotriggerMonitor is the heart of the geotriggers workflow: it takes a single Geotrigger and monitors the condition it defines using real-time changes to the location feed. When a condition is met, it raises an event and provides the info your app needs in order to react appropriately.

Once you have created your GeotriggerMonitor, you need to:

  • Subscribe to notification events so that you will be informed when the condition is met.
  • Watch for warnings that indicate something isn't working correctly.
  • Start the geotrigger monitor to begin monitoring and wait for notifications to start coming in.

The following example creates a geotrigger monitor for the fence geotrigger. Event handlers are added for handling notifications from the monitor (when one of the fence areas is entered). Finally, the monitor is started.

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
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
    // Create a GeotriggerMonitor to monitor the FenceGeotrigger created previously.
    m_geotriggerMonitor = new GeotriggerMonitor(fenceGeotrigger, this);

    // Handle notification events (when a fence is entered).
    connect(m_geotriggerMonitor, &GeotriggerMonitor::geotriggerNotification, this, [this] (GeotriggerNotificationInfo* geotriggerNotificationInfo)
    {

    });

    // Start monitoring.
    QFuture<void> ignored = m_geotriggerMonitor->startAsync();
    Q_UNUSED(ignored);

React to notification events

Whenever your geotrigger monitor is in a started state, you should be ready to handle notifications that provide details for the condition that was met. The GeotriggerNotificationInfo class provides information such as:

  • The location where the condition was met. You can zoom to this position by updating the viewpoint for your map view or scene view.
  • The geoelement for the fence, which includes all of its underlying attributes and attachments.
  • The GeotriggerMonitor that sent the notification. You could use this to stop monitoring once a condition has been met.

When you receive a GeotriggerNotificationInfo you could display a message to the user to tell them what happened. You could take other action, such as selecting a feature on the map or routing to a location. Alternatively, you may not need to interact with the user at all, perhaps writing to a logfile or taking some internal action in your app.

The following example adds the code to handle the geotrigger monitor notifications. The fence attributes are used to determine if this is a suitable feature (charging station). If not, the geotrigger monitor will continue to run. If it is, a graphic will be added to the map view to show the station location and monitoring will stop.

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
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
    // Create a GeotriggerMonitor to monitor the FenceGeotrigger created previously.
    m_geotriggerMonitor = new GeotriggerMonitor(fenceGeotrigger, this);

    // Handle notification events (when a fence is entered).
    connect(m_geotriggerMonitor, &GeotriggerMonitor::geotriggerNotification, this, [this] (GeotriggerNotificationInfo* geotriggerNotificationInfo)
    {

        // Use QScopedPointer to esnure the notification is cleaned up when we exit this scope.
        QScopedPointer<GeotriggerNotificationInfo> scopedNotification{geotriggerNotificationInfo};

        if (geotriggerNotificationInfo->geotriggerNotificationInfoType() !=
            GeotriggerNotificationInfoType::FenceGeotriggerNotificationInfo)
        {
            return;
        }

        FenceGeotriggerNotificationInfo* fenceGeotriggerNotificationInfo = static_cast<FenceGeotriggerNotificationInfo*>(geotriggerNotificationInfo);

        // Get the Fence element that was entered (charging station service area).
        auto fence = fenceGeotriggerNotificationInfo->fenceGeoElement();

        // Get info for this charging station: accessibility, longitude/latitude.
        QString access = fence->attributes()->attributeValue("Accessible").toString().toLower();
        const double stationLongitude = fence->attributes()->attributeValue("Longitude").toDouble();
        const double stationLatitude = fence->attributes()->attributeValue("Latitude").toDouble();

        // If the station requires you to call ahead or is for hotel guests only, return.
        if(access.contains("call ahead") || access.contains("guest use only"))
        {
            return;
        }

        // Show the station for this service area on the map view.
        const Point stationPoint = Point(stationLongitude, stationLatitude, SpatialReference::wgs84());
        Graphic* stationGraphic = new Graphic(stationPoint, m_stationStopSymbol, this);

        m_mapView->graphicsOverlays()->at(0)->graphics()->append(stationGraphic);

        // Stop monitoring.
        m_geotriggerMonitor->stop();

    });

    // Start monitoring.
    QFuture<void> ignored = m_geotriggerMonitor->startAsync();
    Q_UNUSED(ignored);

Customizing Notifications

Optionally, you can provide an expression that defines the message for your Geotrigger, using the Arcade language. This controls the message provided with GeotriggerNotificationInfo that is populated whenever the geotrigger condition is met. The expression uses the Geotrigger Notification Profile, which gives you access to:

  • The $feedfeature (the device location, for example) and its geometry and attributes.
  • Whether the area was "entered" or "exited".
  • The $fencefeature (the target area) and its geometry and attributes.

You can also make use of other functions and objects available in Arcade, such as the Geometry() or Now() functions. For example, the following geotrigger message expression:

"Hello, you have entered " + $fencefeature.name + " with a heading of " + $feedfeature.heading + " degrees at " + Now()

Might result in a GeotriggerNotificationInfo.message of:

Hello, you have entered Area 1 with a heading of 45 degrees at 01 Jan 2021 12:00:00 pm.

Working with Geotriggers when your app is in the background

Many geotrigger workflows monitor and provide notifications while the user is interacting with and viewing the app. Sometimes, however, your app needs to provide notifications for a geotrigger condition while the user is engaged in other tasks, even when the app is not active. When working on mobile devices, it is particularly important to consider how geotriggers will behave when you navigate away from the app. In these scenarios, your app must contain logic that ensures its geotriggers are monitored when running in the background.

To work in the background on a mobile device, your app must enable appropriate settings and permissions for the native operating system.

On iOS devices, the following prerequisites must be satisifed before you are able to receive location events in the background:

  • The app must request authorization from the user to access the device location while it is in the background. You can do this by adding the key NSLocationAlwaysAndWhenInUseUsageDescription to your project's plist file. To learn more about requesting user authorization, see the Note in the Location data sources section of the Device location guide topic.
  • Your project must have Location Updates enabled in the Background Modes. First, add BackgroundModes capability by using the + Capability under Signing & Capabilites of your project's target. Next, check Location Updates from the list of modes listed.
  • You need to set CLLocationManager with the allowsBackgroundLocationUpdates property enabled. Access to an iOS CLLocationManager is provided via the DefaultLocationDataSource.

You can then use the to create a LocationGeotriggerFeed. iOS will give your app execution time for monitoring geotriggers when a location update is received.

When you are finished monitoring geotriggers, you must stop monitoring locations with the iOS Core location services.

On Android devices, one approach is to move the geotriggers business logic to a long running foreground service that you interact with via a persistent notification.

You can continue to show UI such as the map view in your main activity but must ensure that the data required for geotriggers, such as the LocationDataSource and data used for fences, is managed by the foreground service. Your main activity starts the foreground service and then binds to it for sharing data and so on. To receive location updates from the device, you also need to request the ACCESS_BACKGROUND_LOCATION permission.

Once your foreground service is started it will be shown in the device's notification bar irrespective of the state of your app. When a geotrigger condition is met, information can be displayed without relying on the main activity's UI (using a Toast or Snackbar, for example). You can add UI controls to the notification that allow the user to stop geotrigger monitoring.

GPS accuracy considerations for feed updates

Inaccuracies in GPS data can affect the accuracy of geotrigger monitoring. To ensure that geotriggers more accurately report entering and exiting a fence, you can filter the feed updates to only monitor the most suitable locations by using an ArcadeExpression on LocationGeotriggerFeed.filter. For example, the expression return $locationupdate.horizontalaccuracy <= 10.0 only accepts those location updates with a horizontal accuracy less than or equal to 10 meters.

You can factor GPS accuracy into your geotriggers using FenceGeotriggerFeedAccuracyMode.UseGeometryWithAccuracy to buffer locations by their horizontal accuracy. This creates a polygon representing the area of uncertainty around the reported location, and this polygon is used to check if a geotrigger condition is met.

Fence enter and exit spatial relationships

The purpose of your application determines when you want to be notified of the geotriggers. For example, you may choose to receive notifications when you are near a fence or only when you are completely inside it. You can use FenceEnterExitSpatialRelationship to define enter and exit rules for the location with respect to the fence.

Some possible use cases for each FenceEnterExitSpatialRelationship value are described below. Each available value is followed by a diagram that illustrates a corresponding enter and exit scenario. In each diagram, the feed enters the fence from the left (shown in yellow) and exits to the right (shown in purple).

FenceEnterExitSpatialRelationship.EnterIntersectsAndExitDoesNotIntersect: You want to be notified when the location is near a fence, so you choose to receive enter notifications whenever the feed polygon intersects the fence. Exit notifications require that the location is completely outside the fence. For example, an industry worker needs to be notified whenever they are potentially approaching an area with hazardous gases and are also notified when they are clear of the danger.

A geotrigger with spatial relationship enterIntersectsAndExitDoesNotIntersect

FenceEnterExitSpatialRelationship.EnterContainsAndExitDoesNotContain: You want to be notified only when you are completely inside a fence, so you choose to receive enter notifications when the feed polygon is contained within the fence. Exit notifications require that the location is not contained. For example, a field crew sent to cut down trees needs to avoid accidentally cutting down trees in neighboring plots. They only want on-site staff to be notified when there is high confidence.

A geotrigger with spatial relationship enterContainsAndExitDoesNotContain

FenceEnterExitSpatialRelationship.EnterContainsAndExitDoesNotIntersect: You want to be notified only when you are completely inside or outside a fence. For example, a delivery driver driving along a highway past many addresses does not want to be notified of addresses they pass by, but only when they arrive at or leave their destination.

A geotrigger with spatial relationship enterContainsAndExitDoesNotIntersect

Performance considerations

Geotriggers may monitor conditions for long periods of time (maybe the entire time that your app is running). To minimize memory usage and to maximize your device's battery life, consider some factors that can affect the performance of your geotrigger code.

Device specifications determine the limits of what you can successfully monitor. For example, mobile devices with reduced memory capacity can handle fewer fences than a powerful desktop computer. For typical workflows, you can expect mobile devices to effectively monitor tens of thousands of fences and perform well. By comparison, the same geotrigger code running on a desktop machine could handle hundreds of thousands of fences. As a guideline, you should try to monitor the minimum amount of fence data required for your application. Geotriggers use your device's GPS and may continue to run in the background. To reduce your device's battery consumption, turn GPS off when it is not required. Geotriggers are not monitored unless there are location or graphics updates.

You can use multiple geotriggers to monitor features from different feature tables within one application. When using online feature data, there is the possibility of losing network connectivity, which can affect your app's availability. If you need to guarantee consistent performance without relying on network availability, consider taking the fence data offline.

Fence creation considerations

Geotrigger notifications can be affected by several factors including signal strength, accuracy of GPS data, and the frequency of position updates. To ensure accurate notifications, consider the intended speed of travel as well as your device's accuracy when creating fences.

The speed of travel for an app designed for car users is higher than the one designed for pedestrians. For example, if you are traveling at 60 mph (around 100 kph) with location updates every second, the real world distance between two adjacent locations is approximately 30 meters. If the fence was just 10 meters wide, it might fall into the "gap" between positions, and the notification may be missed. Since the distance between position updates is greater when travelling at higher speeds, consider making your fences large enough.

As a general rule, favor larger fences. This helps avoid problems such as

  • Receiving erroneous exit notifications when you're inside a small fence due to GPS drift.
  • Missing an enter notification when you're traveling at high speeds and your locations fall on either side of a small fence.

Also, consider updating your app's location data source to send position updates more frequently, if your performance considerations permit. This allows your app to get more accurate notifications.

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