Skip to content

Commit

Permalink
Update to fix some "inarea" quirks
Browse files Browse the repository at this point in the history
  • Loading branch information
hardillb committed Oct 12, 2017
1 parent 00492b6 commit 1fb59ff
Show file tree
Hide file tree
Showing 5 changed files with 57 additions and 87 deletions.
73 changes: 39 additions & 34 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,44 +3,49 @@ node-red-node-geofence

Geofence node for Node-RED

This uses the geolib node to check if points fall with in a given area. Points are
taken from msg.location.lat & msg.location.lon
This uses the geolib node to check if points fall with in a given region. Points are
taken from the first of the following msg properties in this order:

Areas can be circular or rectangular.
1. msg.location.lat & msg.location.lon
2. msg.lat & msg.lon
3. msg.payload.lat & msg.payload.lon

Messages can be filtered depending on if they fall inside or outside the given area
Region can be circular, rectangular or polygons and are drawn on to a map in the
config node.

![screenshot of config node](https://raw.githubusercontent.com/hardillb/node-red-node-geofence/master/screenshot.png)

Messages can be filtered depending on if they fall inside or outside the given region
or the node can append the node name to a list of areas the msg falls in (to allow the
chaining of geofence nodes).

The list is stored in msg.location.isat in the following format:

```msg.location: {
inarea: true,
isat: [
'firstArea',
'secondArea'
],
distances: {
'firstArea': 100,
'secondArea': 15
}
}```
And also includes the distance from the centre (or centroid for polygons) of the
region.
The config node uses leaflet and openstreetmap data so requires internet access.
You can drop back to a non graphical config by replacing geofence.html with
geofence-nomap.html
The message is augmented by a property called `shapes` which contains shapes of geofence nodes the message traverses when nodes are in `add « inarea » property` action mode.

There is a flow-level property called `shapes` which contains all shapes defined in all geofence nodes in a flow as long as they are hit by a message. (I.e., nodes that are not hit by at least a message will not see their shapes added.)

The following piece of code transforms and sends geofence shapes to a node-red-contrib-web-worldmap node for display (flow level shapes).
It is possible to change shape stroke and fill. See node-red-contrib-web-worldmap for details.

Nodes must have a name. Shapes have the name of their respective nodes.

```
var shapes = flow.get('shapes');
if(shapes) {
Object.keys(shapes).forEach(function(key,index) {
msg.payload = {};
msg.payload.name = key;
if(shapes[key].points) {
msg.payload.area = [];
for(var i = 0; i < shapes[key].points.length; i++) {
msg.payload.area.push([shapes[key].points[i].latitude, shapes[key].points[i].longitude])
}
} else if(shapes[key].radius) {
msg.payload.radius = shapes[key].radius;
msg.payload.lat = shapes[key].centre.latitude;
msg.payload.lon = shapes[key].centre.longitude;
}
node.send(msg);
});
}
```
Possible breaking changes moving from version 0.0.x to 0.1.0.
These are arround the "add 'inarea'" mode
- msg.location.inarea is always a boolean and now just reflects the last geofence
node that the message passes through
- msg.location.distances is now just an object using the name as a key, rather than
an array of objects
16 changes: 10 additions & 6 deletions geofence.html
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,17 @@
<p>It supports circle and polygons, and will filter all messages that either
fall inside or outside the region described depending on the selected
mode.</p>
<p>Alternatively it will add <b>msg.location.inarea</b> to the msg.</p>
<p>If the node is given a name property then <b>msg.location.isat</b> will be an array
containing a list of named areas that the point is inside of and <b>msg.location.distance</b>
will contain an array of name, distance pairs. Where distance is the dinstance in metres to
<p>This node requires input messages one of the following (checked in order):
<ul>
<li><i>msg.location.lat</i> &amp; <i>msg.location.lon</i></li>
<li><i>msg.lat</i> &amp; <i>msg.lon</i></li>
<li><i>msg.payload.lat</i> &amp; <i>msg.payload.lon</li>
</p>
<p>Alternatively it will add <b>msg.location.inarea</b> to the msg. with values of true/false</p>
<p>In this mode if the node is has a name then <b>msg.location.isat</b> will be an array
containing a list of named regions that the point is inside of and <b>msg.location.distance</b>
will contain an object of name, distance pairs. Where distance is the distance in metres to
from the point to the centroid of the region.</p>
<p>This node requires inputs with <i>msg.location.lat</i> &amp; <i>msg.location.lon</i>
or <i>msg.lat</i> &amp; <i>msg.lon</i> values.</p>
</script>

<script type="text/javascript">
Expand Down
53 changes: 7 additions & 46 deletions geofence.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,42 +32,22 @@ module.exports = function(RED) {
node.on('input', function(msg) {
var loc = undefined;

if (msg.location && msg.location.lat && msg.location.lon) {
if (msg.hasOwnProperty('location') && msg.location.hasOwnProperty('lat') && msg.location.hasOwnProperty('lon')) {
loc = {
latitude: msg.location.lat,
longitude: msg.location.lon
};
} else if (msg.lon && msg.lat) {
} else if (msg.hasOwnProperty('lon') && msg.hasOwnProperty('lat')) {
loc = {
latitude: msg.lat,
longitude: msg.lon
};
} else if (typeof(msg.payload) === 'object' && msg.payload.lat && msg.payload.lon) {
} else if (typeof(msg.payload) === 'object' && msg.payload.hasOwnProperty('lat') && msg.payload.hasOwnProperty('lon')) {
loc = {
latitude: msg.payload.lat,
longitude: msg.payload.lon
};
}

if (node.name) {//add shape to list of areas of interest if it has a name
var flowContext = this.context().flow;
var shapes = flowContext.get('shapes') || {};
if(! shapes[node.name]) {
if (node.mode === 'circle') {
shapes[node.name] = {
mode: node.mode,
centre: node.centre,
radius: node.radius
};
} else {
shapes[node.name] = {
mode: node.mode,
points: node.points
};
}
flowContext.set('shapes', shapes);
}
}

if (loc) {
var inout = false;
Expand All @@ -93,11 +73,12 @@ module.exports = function(RED) {
}

if (node.inside === "both") {
if (!msg.location) {
if (!msg.hasOwnProperty("location")) {
msg.location = {};
}

msg.location.inarea = inout;

if (node.name) { // if there is a name
msg.location.isat = msg.location.isat || [];
if (inout) { // if inside then add name to an array
Expand All @@ -111,7 +92,6 @@ module.exports = function(RED) {
}
}
}
msg.location.inarea = msg.location.isat.length;

//add distrance to centroid of area
var distance;
Expand All @@ -121,27 +101,8 @@ module.exports = function(RED) {
var centroid = geolib.getCenter(node.points);
distance = geolib.getDistance(centroid, loc);
}
msg.location.distances = msg.location.distances || [];
var d = {};
d[node.name] = distance;
msg.location.distances.push(d);

var shapes = msg.shapes || {};
if(! shapes[node.name]) {
if (node.mode === 'circle') {
shapes[node.name] = {
mode: node.mode,
centre: node.centre,
radius: node.radius
};
} else {
shapes[node.name] = {
mode: node.mode,
points: node.points
};
}
}

msg.location.distances = msg.location.distances || {};
msg.location.distances[node.name] = distance;
}
node.send(msg);
}
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "node-red-node-geofence",
"version": "0.0.16",
"version": "0.1.0",
"description": "A simple node to filter based on location",
"dependencies" :{
"geolib": "*"
Expand Down
Binary file added screenshot.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 1fb59ff

Please sign in to comment.