Community Tip - Have a PTC product question you need answered fast? Chances are someone has asked it before. Learn about the community search. X
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
}
Hi,
I have created to show a Geofence using Google Map Widget Extension in ThingWorx. However not sure, how to create alerts by linking it to a moving object.
Please note, I have already checked the Axeda integration with ThingWorx at the below link, but clueless on what do do from TWX Composer.
https://community.ptc.com/t5/IoT-Tech-Tips/Axeda-All-about-Geofences/td-p/532750
Appreciate a response.
Thanks