cancel
Showing results for 
Search instead for 
Did you mean: 
cancel
Showing results for 
Search instead for 
Did you mean: 

Axeda: All about Geofences

Aquamarine

Axeda: All about Geofences

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