diff --git a/pom.xml b/pom.xml
index 67a2eef187..1298edab9d 100755
--- a/pom.xml
+++ b/pom.xml
@@ -71,6 +71,8 @@
3.0.2
2.6.1
1.0.0
+ 0.7
+ 1.15.0
1.56
2.0.1
2.4.0
@@ -828,6 +830,16 @@
springfox-swagger-ui-rfc6570
${springfox-swagger-ui-rfc6570.version}
+
+ org.locationtech.spatial4j
+ spatial4j
+ ${spatial4j.version}
+
+
+ org.locationtech.jts
+ jts-core
+ ${jts.version}
+
diff --git a/rule-engine/rule-engine-components/pom.xml b/rule-engine/rule-engine-components/pom.xml
index 01253f0acc..64a6548dc8 100644
--- a/rule-engine/rule-engine-components/pom.xml
+++ b/rule-engine/rule-engine-components/pom.xml
@@ -97,6 +97,14 @@
org.bouncycastle
bcpkix-jdk15on
+
+ org.locationtech.spatial4j
+ spatial4j
+
+
+ org.locationtech.jts
+ jts-core
+
junit
junit
@@ -142,10 +150,10 @@
true
true
-
-
-
-
+
+
+
+
diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/AbstractGeofencingNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/AbstractGeofencingNode.java
new file mode 100644
index 0000000000..f876967917
--- /dev/null
+++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/AbstractGeofencingNode.java
@@ -0,0 +1,120 @@
+/**
+ * Copyright © 2016-2019 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.rule.engine.geo;
+
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+import org.locationtech.spatial4j.context.jts.JtsSpatialContext;
+import org.locationtech.spatial4j.context.jts.JtsSpatialContextFactory;
+import org.springframework.util.StringUtils;
+import org.thingsboard.rule.engine.api.TbContext;
+import org.thingsboard.rule.engine.api.TbNode;
+import org.thingsboard.rule.engine.api.TbNodeConfiguration;
+import org.thingsboard.rule.engine.api.TbNodeException;
+import org.thingsboard.rule.engine.api.util.TbNodeUtils;
+import org.thingsboard.server.common.msg.TbMsg;
+
+import java.util.Collections;
+import java.util.List;
+
+public abstract class AbstractGeofencingNode implements TbNode {
+
+ protected T config;
+ protected JtsSpatialContext jtsCtx;
+
+ @Override
+ public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException {
+ this.config = TbNodeUtils.convert(configuration, getConfigClazz());
+ JtsSpatialContextFactory factory = new JtsSpatialContextFactory();
+ factory.normWrapLongitude = true;
+ jtsCtx = factory.newSpatialContext();
+ }
+
+ abstract protected Class getConfigClazz();
+
+ protected boolean checkMatches(TbMsg msg) throws TbNodeException {
+ JsonElement msgDataElement = new JsonParser().parse(msg.getData());
+ if (!msgDataElement.isJsonObject()) {
+ throw new TbNodeException("Incoming Message is not a valid JSON object");
+ }
+ JsonObject msgDataObj = msgDataElement.getAsJsonObject();
+ double latitude = getValueFromMessageByName(msg, msgDataObj, config.getLatitudeKeyName());
+ double longitude = getValueFromMessageByName(msg, msgDataObj, config.getLongitudeKeyName());
+ List perimeters = getPerimeters(msg, msgDataObj);
+ boolean matches = false;
+ for (Perimeter perimeter : perimeters) {
+ if (checkMatches(perimeter, latitude, longitude)) {
+ matches = true;
+ break;
+ }
+ }
+ return matches;
+ }
+
+ protected boolean checkMatches(Perimeter perimeter, double latitude, double longitude) throws TbNodeException {
+ if (perimeter.getPerimeterType() == PerimeterType.CIRCLE) {
+ Coordinates entityCoordinates = new Coordinates(latitude, longitude);
+ Coordinates perimeterCoordinates = new Coordinates(perimeter.getCenterLatitude(), perimeter.getCenterLongitude());
+ return perimeter.getRange() > GeoUtil.distance(entityCoordinates, perimeterCoordinates, perimeter.getRangeUnit());
+ } else if (perimeter.getPerimeterType() == PerimeterType.POLYGON) {
+ return GeoUtil.contains(perimeter.getPolygonsDefinition(), new Coordinates(latitude, longitude));
+ } else {
+ throw new TbNodeException("Unsupported perimeter type: " + perimeter.getPerimeterType());
+ }
+ }
+
+ protected List getPerimeters(TbMsg msg, JsonObject msgDataObj) throws TbNodeException {
+ if (config.isFetchPerimeterInfoFromMessage()) {
+ //TODO: add fetching perimeters from the message itself, if configuration is empty.
+ if (!StringUtils.isEmpty(msg.getMetaData().getValue("perimeter"))) {
+ Perimeter perimeter = new Perimeter();
+ perimeter.setPerimeterType(PerimeterType.POLYGON);
+ perimeter.setPolygonsDefinition(msg.getMetaData().getValue("perimeter"));
+ return Collections.singletonList(perimeter);
+ } else if (!StringUtils.isEmpty(msg.getMetaData().getValue("centerLatitude"))) {
+ Perimeter perimeter = new Perimeter();
+ perimeter.setPerimeterType(PerimeterType.CIRCLE);
+ perimeter.setCenterLatitude(Double.parseDouble(msg.getMetaData().getValue("centerLatitude")));
+ perimeter.setCenterLongitude(Double.parseDouble(msg.getMetaData().getValue("centerLongitude")));
+ perimeter.setRange(Double.parseDouble(msg.getMetaData().getValue("range")));
+ perimeter.setRangeUnit(RangeUnit.valueOf(msg.getMetaData().getValue("rangeUnit")));
+ return Collections.singletonList(perimeter);
+ } else {
+ throw new TbNodeException("Missing perimeter definition!");
+ }
+ } else {
+ return config.getPerimeters();
+ }
+ }
+
+ protected Double getValueFromMessageByName(TbMsg msg, JsonObject msgDataObj, String keyName) throws TbNodeException {
+ double value;
+ if (msgDataObj.has(keyName) && msgDataObj.get(keyName).isJsonPrimitive()) {
+ value = msgDataObj.get(keyName).getAsDouble();
+ } else {
+ String valueStr = msg.getMetaData().getValue(keyName);
+ if (!StringUtils.isEmpty(valueStr)) {
+ value = Double.parseDouble(valueStr);
+ } else {
+ throw new TbNodeException("Incoming Message has no " + keyName + " in data or metadata!");
+ }
+ }
+ return value;
+ }
+
+
+}
diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/Coordinates.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/Coordinates.java
new file mode 100644
index 0000000000..2dc4bcf6b2
--- /dev/null
+++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/Coordinates.java
@@ -0,0 +1,24 @@
+/**
+ * Copyright © 2016-2019 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.rule.engine.geo;
+
+import lombok.Data;
+
+@Data
+public class Coordinates {
+ private final double latitude;
+ private final double longitude;
+}
diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/EntityGeofencingState.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/EntityGeofencingState.java
new file mode 100644
index 0000000000..4024cbf22e
--- /dev/null
+++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/EntityGeofencingState.java
@@ -0,0 +1,29 @@
+/**
+ * Copyright © 2016-2019 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.rule.engine.geo;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+
+@Data
+@AllArgsConstructor
+public class EntityGeofencingState {
+
+ private boolean inside;
+ private long stateSwitchTime;
+ private boolean staid;
+
+}
diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/GeoUtil.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/GeoUtil.java
new file mode 100644
index 0000000000..3e1918e6bf
--- /dev/null
+++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/GeoUtil.java
@@ -0,0 +1,68 @@
+/**
+ * Copyright © 2016-2019 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.rule.engine.geo;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonParser;
+import org.locationtech.spatial4j.context.SpatialContext;
+import org.locationtech.spatial4j.context.jts.JtsSpatialContext;
+import org.locationtech.spatial4j.context.jts.JtsSpatialContextFactory;
+import org.locationtech.spatial4j.distance.DistanceUtils;
+import org.locationtech.spatial4j.shape.Point;
+import org.locationtech.spatial4j.shape.Shape;
+import org.locationtech.spatial4j.shape.ShapeFactory;
+import org.locationtech.spatial4j.shape.SpatialRelation;
+
+public class GeoUtil {
+
+ private static final SpatialContext distCtx = SpatialContext.GEO;
+ private static final JtsSpatialContext jtsCtx;
+
+ static {
+ JtsSpatialContextFactory factory = new JtsSpatialContextFactory();
+ factory.normWrapLongitude = true;
+ jtsCtx = factory.newSpatialContext();
+ }
+
+ public static synchronized double distance(Coordinates x, Coordinates y, RangeUnit unit) {
+ Point xLL = distCtx.getShapeFactory().pointXY(x.getLongitude(), x.getLatitude());
+ Point yLL = distCtx.getShapeFactory().pointXY(y.getLongitude(), y.getLatitude());
+ return unit.fromKm(distCtx.getDistCalc().distance(xLL, yLL) * DistanceUtils.DEG_TO_KM);
+ }
+
+ public static synchronized boolean contains(String polygon, Coordinates coordinates) {
+ ShapeFactory.PolygonBuilder polygonBuilder = jtsCtx.getShapeFactory().polygon();
+ JsonArray polygonArray = new JsonParser().parse(polygon).getAsJsonArray();
+ boolean first = true;
+ double firstLat = 0.0;
+ double firstLng = 0.0;
+ for (JsonElement jsonElement : polygonArray) {
+ double lat = jsonElement.getAsJsonArray().get(0).getAsDouble();
+ double lng = jsonElement.getAsJsonArray().get(1).getAsDouble();
+ if (first) {
+ firstLat = lat;
+ firstLng = lng;
+ first = false;
+ }
+ polygonBuilder.pointXY(jtsCtx.getShapeFactory().normX(lng), jtsCtx.getShapeFactory().normY(lat));
+ }
+ polygonBuilder.pointXY(jtsCtx.getShapeFactory().normX(firstLng), jtsCtx.getShapeFactory().normY(firstLat));
+ Shape shape = polygonBuilder.buildOrRect();
+ Point point = jtsCtx.makePoint(coordinates.getLongitude(), coordinates.getLatitude());
+ return shape.relate(point).equals(SpatialRelation.CONTAINS);
+ }
+}
diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/Perimeter.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/Perimeter.java
new file mode 100644
index 0000000000..6ae3372d9f
--- /dev/null
+++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/Perimeter.java
@@ -0,0 +1,34 @@
+/**
+ * Copyright © 2016-2019 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.rule.engine.geo;
+
+import lombok.Data;
+
+@Data
+public class Perimeter {
+
+ private PerimeterType perimeterType;
+
+ //For Polygons
+ private String polygonsDefinition;
+
+ //For Circles
+ private Double centerLatitude;
+ private Double centerLongitude;
+ private Double range;
+ private RangeUnit rangeUnit;
+
+}
diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/PerimeterType.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/PerimeterType.java
new file mode 100644
index 0000000000..8f9d86370d
--- /dev/null
+++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/PerimeterType.java
@@ -0,0 +1,20 @@
+/**
+ * Copyright © 2016-2019 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.rule.engine.geo;
+
+public enum PerimeterType {
+ CIRCLE, POLYGON
+}
diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/RangeUnit.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/RangeUnit.java
new file mode 100644
index 0000000000..509d829552
--- /dev/null
+++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/RangeUnit.java
@@ -0,0 +1,30 @@
+/**
+ * Copyright © 2016-2019 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.rule.engine.geo;
+
+public enum RangeUnit {
+ METER(1000.0), KILOMETER(1.0), FOOT(3280.84), MILE(0.62137), NAUTICAL_MILE(0.539957);
+
+ private final double fromKm;
+
+ RangeUnit(double fromKm) {
+ this.fromKm = fromKm;
+ }
+
+ public double fromKm(double v) {
+ return v * fromKm;
+ }
+}
diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingActionNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingActionNode.java
new file mode 100644
index 0000000000..952109286f
--- /dev/null
+++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingActionNode.java
@@ -0,0 +1,121 @@
+/**
+ * Copyright © 2016-2019 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.rule.engine.geo;
+
+import com.google.gson.Gson;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+import lombok.extern.slf4j.Slf4j;
+import org.thingsboard.rule.engine.api.RuleNode;
+import org.thingsboard.rule.engine.api.TbContext;
+import org.thingsboard.rule.engine.api.TbNodeException;
+import org.thingsboard.server.common.data.DataConstants;
+import org.thingsboard.server.common.data.id.EntityId;
+import org.thingsboard.server.common.data.kv.AttributeKvEntry;
+import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry;
+import org.thingsboard.server.common.data.kv.StringDataEntry;
+import org.thingsboard.server.common.data.plugin.ComponentType;
+import org.thingsboard.server.common.msg.TbMsg;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * Created by ashvayka on 19.01.18.
+ */
+@Slf4j
+@RuleNode(
+ type = ComponentType.ACTION,
+ name = "gps geofencing events",
+ configClazz = TbGpsGeofencingFilterNodeConfiguration.class,
+ relationTypes = {"Entered", "Left", "Stays Inside", "Stays Outside"},
+ nodeDescription = "Produces incoming messages using GPS based geofencing",
+ nodeDetails = "Extracts latitude and longitude parameters from incoming message and returns different events based on configuration parameters")
+public class TbGpsGeofencingActionNode extends AbstractGeofencingNode {
+
+ private final Map entityStates = new HashMap<>();
+ private final Gson gson = new Gson();
+ private final JsonParser parser = new JsonParser();
+
+ @Override
+ public void onMsg(TbContext ctx, TbMsg msg) throws TbNodeException {
+ boolean matches = checkMatches(msg);
+ long ts = System.currentTimeMillis();
+
+ EntityGeofencingState entityState = entityStates.computeIfAbsent(msg.getOriginator(), key -> {
+ try {
+ Optional entry = ctx.getAttributesService()
+ .find(ctx.getTenantId(), msg.getOriginator(), DataConstants.SERVER_SCOPE, ctx.getNodeId())
+ .get(1, TimeUnit.MINUTES);
+ if (entry.isPresent()) {
+ JsonObject element = parser.parse(entry.get().getValueAsString()).getAsJsonObject();
+ return new EntityGeofencingState(element.get("inside").getAsBoolean(), element.get("stateSwitchTime").getAsLong(), element.get("staid").getAsBoolean());
+ } else {
+ return new EntityGeofencingState(false, 0L, false);
+ }
+ } catch (InterruptedException | TimeoutException | ExecutionException e) {
+ throw new RuntimeException(e);
+ }
+ });
+ if (entityState.getStateSwitchTime() == 0L || entityState.isInside() != matches) {
+ switchState(ctx, msg.getOriginator(), entityState, matches, ts);
+ ctx.tellNext(msg, matches ? "Entered" : "Left");
+ } else if (!entityState.isStaid()) {
+ long stayTime = ts - entityState.getStateSwitchTime();
+ if (stayTime > (entityState.isInside() ? config.getMinInsideDuration() : config.getMinOutsideDuration())) {
+ setStaid(ctx, msg.getOriginator(), entityState);
+ }
+ }
+ }
+
+ private void switchState(TbContext ctx, EntityId entityId, EntityGeofencingState entityState, boolean matches, long ts) {
+ entityState.setInside(matches);
+ entityState.setStateSwitchTime(ts);
+ entityState.setStaid(false);
+ persist(ctx, entityId, entityState);
+ }
+
+ private void setStaid(TbContext ctx, EntityId entityId, EntityGeofencingState entityState) {
+ entityState.setStaid(true);
+ persist(ctx, entityId, entityState);
+ }
+
+ private void persist(TbContext ctx, EntityId entityId, EntityGeofencingState entityState) {
+ JsonObject object = new JsonObject();
+ object.addProperty("inside", entityState.isInside());
+ object.addProperty("stateSwitchTime", entityState.getStateSwitchTime());
+ object.addProperty("staid", entityState.isStaid());
+ AttributeKvEntry entry = new BaseAttributeKvEntry(new StringDataEntry(ctx.getNodeId(), gson.toJson(object)), System.currentTimeMillis());
+ List attributeKvEntryList = Collections.singletonList(entry);
+ ctx.getAttributesService().save(ctx.getTenantId(), entityId, DataConstants.SERVER_SCOPE, attributeKvEntryList);
+ }
+
+ @Override
+ public void destroy() {
+
+ }
+
+ @Override
+ protected Class getConfigClazz() {
+ return TbGpsGeofencingActionNodeConfiguration.class;
+ }
+}
diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingActionNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingActionNodeConfiguration.java
new file mode 100644
index 0000000000..dac9e7d23d
--- /dev/null
+++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingActionNodeConfiguration.java
@@ -0,0 +1,45 @@
+/**
+ * Copyright © 2016-2019 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.rule.engine.geo;
+
+import lombok.Data;
+import org.thingsboard.rule.engine.api.NodeConfiguration;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Created by ashvayka on 19.01.18.
+ */
+@Data
+public class TbGpsGeofencingActionNodeConfiguration extends TbGpsGeofencingFilterNodeConfiguration {
+
+ private double minInsideDuration;
+ private double minOutsideDuration;
+
+ @Override
+ public TbGpsGeofencingActionNodeConfiguration defaultConfiguration() {
+ TbGpsGeofencingActionNodeConfiguration configuration = new TbGpsGeofencingActionNodeConfiguration();
+ configuration.setLatitudeKeyName("latitude");
+ configuration.setLongitudeKeyName("longitude");
+ configuration.setFetchPerimeterInfoFromMessage(true);
+ configuration.setPerimeters(Collections.emptyList());
+ configuration.setMinInsideDuration(TimeUnit.MINUTES.toMillis(1));
+ configuration.setMinOutsideDuration(TimeUnit.MINUTES.toMillis(1));
+ return configuration;
+ }
+}
diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingFilterNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingFilterNode.java
new file mode 100644
index 0000000000..66a23ca2a8
--- /dev/null
+++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingFilterNode.java
@@ -0,0 +1,71 @@
+/**
+ * Copyright © 2016-2019 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.rule.engine.geo;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+import lombok.extern.slf4j.Slf4j;
+import org.locationtech.spatial4j.context.jts.JtsSpatialContext;
+import org.locationtech.spatial4j.context.jts.JtsSpatialContextFactory;
+import org.locationtech.spatial4j.shape.Point;
+import org.locationtech.spatial4j.shape.Shape;
+import org.locationtech.spatial4j.shape.ShapeFactory;
+import org.locationtech.spatial4j.shape.SpatialRelation;
+import org.springframework.util.StringUtils;
+import org.thingsboard.rule.engine.api.RuleNode;
+import org.thingsboard.rule.engine.api.TbContext;
+import org.thingsboard.rule.engine.api.TbNode;
+import org.thingsboard.rule.engine.api.TbNodeConfiguration;
+import org.thingsboard.rule.engine.api.TbNodeException;
+import org.thingsboard.rule.engine.api.util.TbNodeUtils;
+import org.thingsboard.rule.engine.filter.TbMsgTypeFilterNodeConfiguration;
+import org.thingsboard.server.common.data.plugin.ComponentType;
+import org.thingsboard.server.common.msg.TbMsg;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Created by ashvayka on 19.01.18.
+ */
+@Slf4j
+@RuleNode(
+ type = ComponentType.FILTER,
+ name = "gps geofencing filter",
+ configClazz = TbGpsGeofencingFilterNodeConfiguration.class,
+ relationTypes = {"True", "False"},
+ nodeDescription = "Filter incoming messages by GPS based geofencing",
+ nodeDetails = "Extracts latitude and longitude parameters from incoming message and returns 'True' if they are inside configured perimeters, 'False' otherwise.")
+public class TbGpsGeofencingFilterNode extends AbstractGeofencingNode {
+
+ @Override
+ public void onMsg(TbContext ctx, TbMsg msg) throws TbNodeException {
+ boolean matches = checkMatches(msg);
+ ctx.tellNext(msg, matches ? "True" : "False");
+ }
+
+ @Override
+ public void destroy() {
+
+ }
+
+ @Override
+ protected Class getConfigClazz() {
+ return TbGpsGeofencingFilterNodeConfiguration.class;
+ }
+}
diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingFilterNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingFilterNodeConfiguration.java
new file mode 100644
index 0000000000..7d54699741
--- /dev/null
+++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingFilterNodeConfiguration.java
@@ -0,0 +1,47 @@
+/**
+ * Copyright © 2016-2019 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.rule.engine.geo;
+
+import lombok.Data;
+import org.thingsboard.rule.engine.api.NodeConfiguration;
+import org.thingsboard.server.common.data.EntityType;
+import org.thingsboard.server.common.msg.session.SessionMsgType;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Created by ashvayka on 19.01.18.
+ */
+@Data
+public class TbGpsGeofencingFilterNodeConfiguration implements NodeConfiguration {
+
+ private String latitudeKeyName;
+ private String longitudeKeyName;
+ private boolean fetchPerimeterInfoFromMessage;
+ private List perimeters;
+
+ @Override
+ public TbGpsGeofencingFilterNodeConfiguration defaultConfiguration() {
+ TbGpsGeofencingFilterNodeConfiguration configuration = new TbGpsGeofencingFilterNodeConfiguration();
+ configuration.setLatitudeKeyName("latitude");
+ configuration.setLongitudeKeyName("longitude");
+ configuration.setFetchPerimeterInfoFromMessage(true);
+ configuration.setPerimeters(Collections.emptyList());
+ return configuration;
+ }
+}