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 AGSLocationDataSource you choose, while tapping into the full power of ArcGIS Runtime's AGSGeometryEngine 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 AGSGeotrigger defines a condition that you want to monitor as the device location changes. For monitoring conditions based on a spatial relationship, ArcGIS Runtime provides the AGSFenceGeotrigger class.

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" AGSGeotriggerFeed
Spatial operation"comes within" AGSFenceRuleType
Areas (fences) to check"my target areas" AGSFenceParameters

Geotrigger feed

To monitor your device location, pass a AGSLocationDataSource to a AGSLocationGeotriggerFeed . The AGSLocationDataSource is also what you use with the AGSLocationDisplay to show your position on a map.

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

The following example creates a simulated location data source that follows a provided route polyline. 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

// Create a new simulated location data source.
let locationDataSource = AGSSimulatedLocationDataSource()
// The simulated location will move across the provided polyline.
locationDataSource.setLocationsWith(routeLine)
// ...
// Create a new Geotrigger feed with the simulated location source.
let feed = AGSLocationGeotriggerFeed(locationDataSource: locationDataSource)
// ...
// Enable location display on the map view using the same simulated location source.
self.mapView.locationDisplay.dataSource = locationDataSource
self.mapView.locationDisplay.start { error in
    if let error = error {
        print(error.localizedDescription)
    }
}

Fence parameters

You supply a AGSFenceParameters 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 AGSGraphicFenceParameters or a AGSGraphicsOverlayFenceParameters .

  • Features from a AGSFeatureTable 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 AGSFeatureFenceParameters 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
// Create parameters that define the fence features and a buffer distance (meters).
let fenceParams = AGSFeatureFenceParameters(featureTable: driveTimeServiceAreas, bufferDistance: 50.0)
// Create a geotrigger with the location feed, "enter" rule type, and the fence parameters.
let fenceGeotrigger = AGSFenceGeotrigger(feed: feed, ruleType: .enter, fenceParameters: fenceParams)

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 AGSGeotriggerMonitor is the heart of the geotriggers workflow: it takes a single AGSGeotrigger 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 AGSGeotriggerMonitor , 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 to monitor the fence geotrigger. An event handler is 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
// Create a GeotriggerMonitor to monitor the fence geotrigger created previously.
let geotriggerMonitor = AGSGeotriggerMonitor(geotrigger: fenceGeotrigger)
// Handle notification events (when a fence is entered).
_ = NotificationCenter.default.addObserver(
    forName: .AGSGeotriggerMonitorDidTrigger,
    object: geotriggerMonitor,
    queue: nil,
    using: { [weak self] note in self?.handleGeotriggerNotification(note) }
)
// Start monitoring.
geotriggerMonitor.start { error in
    if let error = error {
        print(error.localizedDescription)
    }
}

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 AGSGeotriggerNotificationInfo 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 AGSGeotriggerMonitor that sent the notification. You could use this to stop monitoring once a condition has been met.

When you receive a AGSGeotriggerNotificationInfo 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 handles geotrigger monitor notifications. fenceNotificationInfo is used to get the fence feature. 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
func handleGeotriggerNotification(_ notification: Notification) {
    guard let fenceNotificationInfo = notification.userInfo?[AGSGeotriggerNotificationInfoKey] as? AGSFenceGeotriggerNotificationInfo else { return }

    // Get the fence geoelement that was entered (charging station service area).
    let serviceAreaFeature = fenceNotificationInfo.fenceGeoElement as! AGSArcGISFeature

    // Get info for this charging station: accessibility, longitude/latitude.
    guard let access = (serviceAreaFeature.attributes["Accessible"] as? String)?.lowercased() else { return }
    guard let stationLatitude = serviceAreaFeature.attributes["Latitude"] as? Double else { return }
    guard let stationLongitude = serviceAreaFeature.attributes["Longitude"] as? Double else { return }

    // 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.
    let stationPoint = AGSPoint(x: stationLongitude, y: stationLatitude, spatialReference: .wgs84())
    let stationGraphic = AGSGraphic(geometry: stationPoint, symbol: stationStopSymbol, attributes: nil)

    // Add the graphic to the map view.
    let graphicsOverlay = AGSGraphicsOverlay()
    graphicsOverlay.graphics.add(stationGraphic)
    mapView.graphicsOverlays.add(graphicsOverlay)

    // Stop monitoring.
    geotriggerMonitor.stop()
}

Customizing Notifications

Optionally, you can provide an expression that defines the message for your AGSGeotrigger , using the Arcade language. This controls the message provided with AGSGeotriggerNotificationInfo 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.

On iOS devices, the following are the prerequisites that 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. ArcGIS Runtime provides access to an iOS CLLocationManager via the AGSCLLocationDataSource

You can then use the AGSCLLocationDataSource to create a AGSLocationGeotriggerFeed . 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.

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 AGSArcadeExpression on AGSLocationGeotriggerFeed.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 AGSFenceGeotriggerFeedAccuracyMode.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 AGSFenceEnterExitSpatialRelationship to define enter and exit rules for the location with respect to the fence.

Some possible use cases for each AGSFenceEnterExitSpatialRelationship 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).

AGSFenceEnterExitSpatialRelationship.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

AGSFenceEnterExitSpatialRelationship.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

AGSFenceEnterExitSpatialRelationship.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.

If your app uses Runtime versions prior to 100.13, when using online features as fences, the total number of fences you can monitor is limited by the service's maximum feature count (usually 1,000 - 2,000 features). To create fences beyond that limit, you can split the features between several geotriggers.

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.