Implementing an Analysis to notify when a device is inside of a geofence

Here I will show how to implement an Analysis to notify me if my device is inside of any geofence.
Captura de tela de 2021-01-18 15-42-07

In this tutorial, I use Events’ code as a name, and when I send a notification is this name that will appear at the message.

Also, I store the Devices’ name at the variable that I have the location of the Device.

 {
   "variable": "location",
   "value": "Device name here",
   "location": {
     "lat": 35.770723,
     "lng": -78.677328
   }
 }

In fact, I am just showing one of many ways to do something like that. You can use this example the way you prefer.

1 - Copy this code below and paste it on a new NodeJS’s Analysis

const { Utils, Account, Analysis, Device, Services } = require("@tago-io/sdk");
const geolib = require("geolib");
// This function checks if our device is inside a polygon geofence
function insidePolygon(point, geofence) {
  const x = point[1];
  const y = point[0];
  let inside = false;
  for (let i = 0, j = geofence.length - 1; i < geofence.length; j = i++) {
    const xi = geofence[i][0];
    const yi = geofence[i][1];
    const xj = geofence[j][0];
    const yj = geofence[j][1];
    const intersect = yi > y != yj > y && x < ((xj - xi) * (y - yi)) / (yj - yi) + xi;
    if (intersect) inside = !inside;
  }
  return inside;
}

// This function checks if our device is inside any geofence
function checkZones(point, geofence_list) {
  const insideZones = [];
  // The line below gets all Polygon geofences that we may have.
  const polygons = geofence_list.filter((x) => x.geolocation.type === "Polygon");
  if (polygons.length) {
    // Here we check if our device is inside any Polygon geofence using our function above.
    for (const polygon of polygons) {
        if (insidePolygon(point,  polygon.geolocation.coordinates[0])){
            insideZones.push(polygon.event);
        }
    }
  }
  // The line below gets all Point (circle) geofences that we may have.
  const circles = geofence_list.filter((x) => x.geolocation.type === "Point");
  if (circles.length) {
    // Here we check if our device is inside any Point geofence using a third party library called geolib.
    for (const circle of circles) {
        if(  
            geolib.isPointWithinRadius(
            { latitude: point[1], longitude: point[0] },
            { latitude: circle.geolocation.coordinates[0], longitude: circle.geolocation.coordinates[1] },
            circle.geolocation.radius
        )) {
            insideZones.push(circle.event);
        }
    }
  }
  return insideZones;
}

// This function help us get the device using just its id.
async function getDevice(account, device_id) {
  const customer_token = await Utils.getTokenByName(account, device_id);
  const customer_dev = new Device({ token: customer_token });
  return customer_dev;
}

async function startAnalysis(context, scope) {
  context.log("Running");

  if (!scope[0]) throw "Scope is missing"; // doesn't need to run if scope[0] is null

  // The code block below gets all environment variables and checks if we have the needed ones.
  const environment = Utils.envToJson(context.environment);
  if (!environment.account_token) throw "Missing account_token environment var";

  // The line below starts our notification service.
  const notification = new Services({ token: context.token }).Notification;

   const account = new Account({ token: environment.account_token });
  const device_id = scope[0].origin;

  // Here we get the device information using our account data and the device id.
  const device = await getDevice(account, device_id);

  // This checks if we received a location
  const location = scope.find((data) => data.variable === "YourLocationVariable");
  if (!location || !location.location) return context.log("No location found in the scope.");
  // Now we check if we have any geofences to go through.
  const geofences = await device.getData({ variable: "YourGeofenceVariableName", qty: 10 });
  const zones = geofences.map((geofence) => geofence.metadata);

  const insideZones = checkZones(location.location.coordinates, zones);
  if (insideZones && insideZones.length > 0) {
    const notificationMessage = `The ${location.value} is inside of ${insideZones.length > 1 ? "these" : "this"} geofence ${insideZones.join(", ")}`;
    notification.send({ title: "Inside geofence", message: notificationMessage });
    return;
  }
}

module.exports = new Analysis(startAnalysis);

Change YourLocationVariable for the variable that has the location coordinates of the device, YourGeofenceVariableName for the variable that is used to store the geofences, and include your account_token at Environment variables.

2 - Create an Action to trigger the Analysis when devices receive an update at the location variable.

In this action, you will choose which device do you want to trigger the analysis, in my case I didn’t put a condition, so every time that I receive an update of the location, the analysis will run.

That’s it, with these steps you can build receive notifications every time a device enters a Geofence. This code can be adapted to notify when the device is not inside of any geofence as well, you just need to include a new notification.send before the function startAnalysis finish.

Thanks!

2 Likes

Hello,

I followed this example to implement something similar and I have a question.

Considering my devices send data every few seconds whenever they are moved, I would like to avoid having to run a lot of analysis, I would like to lock the action to only run the analysis once when it detects the device to be inside the geofence and unlock it when it detects it to be outside but also only run the analysis once and lock again until the device goes inside.

What is the proper way to implement an action lock with this example?

Would this require 2 actions per device in order to do this, one for inside the geofence and one for outside?

What variable would be required to detect this outside of the analysis? Could I potentially use a geofence event to consider whether the device is in or out? I have considered using lat and long to do this, but this would be very rigid considering I would need the change the conditions every time I might want to move the geofence.

Thanks in advance for your help.

I’m not sure how to do what you’re asking.

If the problem is the number of analysis run, I would go another way, but not sure if it makes sense for you.

Instead of running each time the device sends data, you can create a schedule action to run an analysis every 1 minute. Then you can get all your devices by a given tag, and check the last_input to see if it has any new data.

If it has, then you get the last position of the asset and check if it’s inside of a geofence. That way you will set the analysis to be 60/hour with any given amount of devices. The downside is that any alert would delay ~1 minute, and if you’re checking only last position, it can be that your device did go in and out of a geofence but you will be checking only the last position for that given time.

1 Like

Hello Vitor,

Yeah, this approach doesn’t work with what I wanted to achieve. I wanted it to sent the notification right when it goes in or out of the geofence. But it is fine if it doesn’t work, I was only trying to see if it was possible.

Thanks for your help.

1 Like