Requirements: 6.1.2+ Geofences are geometric shapes drawn virtually on a geographical area that represents a fence that can be crossed by a device. The Axeda Platform has built-in support for mobile locations and geofences, which can be linked to the rules engine to enable notifications based on geofence crossing. What this tutorial covers This tutorial demonstrates the workflow of creating a geofence through to creating the expression rules with notifications, then how the mobile location can trigger the rules. 1) Creating the Geofence 2) Creating the Expression Rule There is currently no user interface built into the Axeda Applications Console which interacts with geofences. For a sample application with a geofence user interface, see Sample Project: Traxeda (TODO). For a single Custom Object that includes all of the functionality described below, see the end of this document. The properties of a geofence are a name, a description, and a series of coordinates based on Well-Known Text (WKT) syntax (see the OpenGIS Simple Features Specification). def addGeofence(CONTEXT, map){ Geofence myGeofence = new Geofence(CONTEXT) myGeofence.name = map.name if(map.type != "polygon" && map.type != "circle") { throw new Exception("Invalid type: need 'polygon' or 'circle', not '$map.type'") } else if(map.type == "polygon") { def geo = map.locs.loc.inject( "POLYGON (("){ str, item -> def lng = item.lng def lat = item.lat str += "$lng $lat," str } //the first location also has to be the last location myGeofence.geometry = geo + map.locs.loc[0].lng + " " + map.locs.loc[0].lat + "))" //Something like this is built: //POLYGON ((-71.082118 42.383892,-70.867198 42.540923,-71.203654 42.495374,-71.284678 42.349394,-71.163829 42.221382,-71.003154 42.266114,-71.082118 42.383892)) } else if(map.type == "circle") { def lng = map.locs.loc[0].lng def lat = map.locs.loc[0].lat myGeofence.geometry = "POINT ($lng $lat)" //POINT (-71.082118 42.383892) myGeofence.buffer = map.radius.toDouble() } myGeofence.description = "ALERT:::$map.alertType:::$map.alert" try { myGeofence.store() } catch (e){ logger.info e.localizedMessage return null } myGeofence } The geofence itself does not interact with devices in any way. Rather it is the Expression Rule that is applied to models and devices and that invokes the geofence when a mobile location is passed in. Creating the Expression Rule The Expression Rule for the Geofence is built as follows: TYPE: MobileLocation IF: Expression set to "InNamedGeofence" for entering and "!InNamedGeofence" for exiting. The following function creates this expression rule: /* Sample call createGeofenceExpressionRule(CONTEXT, "My Geofence", "rule_MyGeofence", "in", "You entered the geofence!", "SDK Generated Geofence Rule", 100) */ def createGeofenceExpressionRule(com.axeda.drm.sdk.Context CONTEXT, String geofencename, String rulename, String alertType, String alertMessage, String ruledescription, int severity){ ExpressionRuleFinder erf = new ExpressionRuleFinder(CONTEXT) erf.setName(rulename) ExpressionRule expressionRule1 = erf.findOne() expressionRule1?.delete() def expressionRule = new ExpressionRule(CONTEXT) expressionRule.setName(rulename) expressionRule.setDescription(ruledescription) expressionRule.setTriggerName("MobileLocation") def ifExpStr = "InNamedGeofence(\"$geofencename\", Location.location)" if(alertType == "out"){ ifExpStr = "!" + ifExpStr } expressionRule.setIfExpression(new Expression(ifExpStr)) expressionRule.setThenExpression(new Expression("CreateAlarm(\"$alertMessage\", severity)")) expressionRule.setEnabled(true) expressionRule.setConsecutive(false) expressionRule.store() expressionRule } Then the rule associations must be created to apply the rule to a model or device. /* Sample call findOrCreateRuleAssociations(CONTEXT, myModel, expressionRule, "EXPRESSION_RULE", "MODEL") Where expressionRule is the rule created in the above example */ def findOrCreateRuleAssociations(Context CONTEXT, Object entity, Object rule, String ruleType, String entityType){ // rule type is whether this is an expression rule ruleType = ruleType ?: "EXPRESSION_RULE" entityType = entityType ?: "DEVICE_INCLUDE" RuleAssociationFinder ruleAssociationFinder = new RuleAssociationFinder(CONTEXT) ruleAssociationFinder.setRuleId(rule.id.value) ruleAssociationFinder.setRuleType(RuleType.valueOf(ruleType)) ruleAssociationFinder.setEntityId(entity.id.value) ruleAssociationFinder.setEntityType(EntityType.valueOf(entityType)) def ruleAssociations = ruleAssociationFinder.findAll() if (!ruleAssociations || ruleAssociations?.size() == 0){ def ruleAssociation = new RuleAssociation(CONTEXT) ruleAssociation.entityId = entity.id.value ruleAssociation.entityType = EntityType.valueOf(entityType) ruleAssociation.ruleType = RuleType.valueOf(ruleType) ruleAssociation.setRuleId(rule.id.value) ruleAssociation.store() ruleAssociations = [ruleAssociation] } return ruleAssociations } The rule will now be triggered when any device of the applied model sends a mobile location within the geofence, which in turn will create an alarm. Here is a custom object with the complete geofence functionality: import com.axeda.drm.sdk.Context import com.axeda.drm.sdk.geofence.Geofence import com.axeda.drm.sdk.geofence.GeofenceFinder import com.axeda.drm.sdk.rules.engine.Expression import com.axeda.drm.sdk.rules.engine.ExpressionRule import com.axeda.drm.sdk.rules.engine.ExpressionRuleFinder import com.axeda.drm.sdk.rules.engine.RuleAssociation import com.axeda.drm.sdk.rules.engine.RuleAssociationFinder import com.axeda.drm.sdk.rules.engine.RuleType import com.axeda.drm.sdk.common.EntityType import com.axeda.drm.sdk.device.Model import com.axeda.drm.sdk.device.ModelFinder try { def Context CONTEXT = Context.getSDKContext() def model = findOrCreateModel(CONTEXT, "FooModel") def sampleCircle = [ "name": "My Circle", "alert": "My Geofence Alert Text", "type": "circle", "alertType": "in", "radius": "65.76", "locs": [ [ "loc": [ "lat": "42.60970621339408", "lng": "-73.201904296875" ] ] ] ] def samplePolygon = [ "name": "My Polygon", "alert": "My Geofence Alert Text", "type": "polygon", "alertType": "out", "locs": [ ["loc": [ "lng": -71.2604999542236, "lat": 42.3384903145478 ]], ["loc": [ "lng": -71.4218616485596, "lat": 42.3242772020001 ]], ["loc": [ "lng": -71.5585041046143, "lat": 42.2653600946699 ]], ["loc": [ "lng": -71.5413379669189, "lat": 42.1885837119108 ]], ["loc": [ "lng": -71.4719867706299, "lat": 42.1137514551207 ]], ["loc": [ "lng": -71.3737964630127, "lat": 42.0398506628541 ]], ["loc": [ "lng": -71.2508869171143, "lat": 42.0311807962068 ]], ["loc": [ "lng": -71.1355304718018, "lat": 42.2084223174036 ]], ["loc": [ "lng": -71.2604999542236, "lat": 42.3384903145478 ]] ] ] // find geofence if it exists def circle = findGeofenceByName(CONTEXT, sampleCircle.name) // create circular geofence if (!circle){ circle = addGeofence(CONTEXT, sampleCircle) } // create rule for circular geofence def circleRule = createGeofenceExpressionRule(CONTEXT, circle.name, "${circle.name}__Rule", sampleCircle.alertType, sampleCircle.alert, "SDK Generated Geofence Rule", 100) // apply rule to new Model findOrCreateRuleAssociations(CONTEXT, model, circleRule, "EXPRESSION_RULE", "MODEL") def polygon = findGeofenceByName(CONTEXT, samplePolygon.name) if (!polygon){ polygon = addGeofence(CONTEXT, samplePolygon) } def polygonRule = createGeofenceExpressionRule(CONTEXT, polygon.name, "${polygon.name}__Rule", samplePolygon.alertType, samplePolygon.alert, "SDK Generated Geofence Rule", 100) // apply rule to new Model findOrCreateRuleAssociations(CONTEXT, model, polygonRule, "EXPRESSION_RULE", "MODEL") } catch (Exception e) { logger.info(e.localizedMessage) } return true def findGeofenceByName(CONTEXT, name){ GeofenceFinder geofenceFinder = new GeofenceFinder(CONTEXT) geofenceFinder.setName(name) def geofence = geofenceFinder.find() geofence } def addGeofence(CONTEXT, map){ Geofence myGeofence = new Geofence(CONTEXT) myGeofence.name = map.name if(map.type != "polygon" && map.type != "circle") { throw new Exception("Invalid type: need 'polygon' or 'circle', not '$map.type'") } else if(map.type == "polygon") { def geo = map.locs.loc.inject( "POLYGON (("){ str, item -> def lng = item.lng def lat = item.lat str += "$lng $lat," str } //the first location also has to be the last location myGeofence.geometry = geo + map.locs.loc[0].lng + " " + map.locs.loc[0].lat + "))" //Something like this is built: //POLYGON ((-71.082118 42.383892,-70.867198 42.540923,-71.203654 42.495374,-71.284678 42.349394,-71.163829 42.221382,-71.003154 42.266114,-71.082118 42.383892)) } else if(map.type == "circle") { def lng = map.locs.loc[0].lng def lat = map.locs.loc[0].lat myGeofence.geometry = "POINT ($lng $lat)" //POINT (-71.082118 42.383892) myGeofence.buffer = map.radius.toDouble() } myGeofence.description = "ALERT:::$map.alertType:::$map.alert" try { myGeofence.store() } catch (e) { logger.info e.localizedMessage return null } myGeofence } def createGeofenceExpressionRule(com.axeda.drm.sdk.Context CONTEXT, String geofencename, String rulename, String alertType, String alertMessage, String ruledescription, int severity) { ExpressionRuleFinder erf = new ExpressionRuleFinder(CONTEXT) erf.setName(rulename) ExpressionRule expressionRule1 = erf.findOne() expressionRule1?.delete() def expressionRule = new ExpressionRule(CONTEXT) expressionRule.setName(rulename) expressionRule.setDescription(ruledescription) expressionRule.setTriggerName("MobileLocation") def ifExpStr = "InNamedGeofence(\"$geofencename\", Location.location)" if(alertType == "out"){ ifExpStr = "!" + ifExpStr } expressionRule.setIfExpression(new Expression(ifExpStr)) expressionRule.setThenExpression(new Expression("CreateAlarm(\"$alertMessage\", severity)")) expressionRule.setEnabled(true) expressionRule.setConsecutive(false) expressionRule.store() expressionRule } def findOrCreateRuleAssociations(Context CONTEXT, Object entity, Object rule, String ruleType, String entityType) { // rule type is whether this is an expression rule ruleType = ruleType ?: "EXPRESSION_RULE" entityType = entityType ?: "DEVICE_INCLUDE" RuleAssociationFinder ruleAssociationFinder = new RuleAssociationFinder(CONTEXT) ruleAssociationFinder.setRuleId(rule.id.value) ruleAssociationFinder.setRuleType(RuleType.valueOf(ruleType)) ruleAssociationFinder.setEntityId(entity.id.value) ruleAssociationFinder.setEntityType(EntityType.valueOf(entityType)) def ruleAssociations = ruleAssociationFinder.findAll() if (!ruleAssociations || ruleAssociations?.size() == 0){ def ruleAssociation = new RuleAssociation(CONTEXT) ruleAssociation.entityId = entity.id.value ruleAssociation.entityType = EntityType.valueOf(entityType) ruleAssociation.ruleType = RuleType.valueOf(ruleType) ruleAssociation.setRuleId(rule.id.value) ruleAssociation.store() ruleAssociations = [ruleAssociation] } return ruleAssociations } def findOrCreateModel(Context CONTEXT, String modelName) { ModelFinder modelFinder = new ModelFinder(CONTEXT) modelFinder.setName(modelName) def model = modelFinder.find() if (!model){ model = new Model(CONTEXT, modelName); model.store(); } return model } https://gist.github.com/axeda/6529288/raw/5ffca58c3c48256b81287d6a6f2d2db63cd5cd2b/AddGeofence.groovy
View full tip