13 changed files with 633 additions and 4 deletions
@ -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<T extends TbGpsGeofencingFilterNodeConfiguration> 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<T> 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<Perimeter> 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<Perimeter> 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; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
@ -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; |
||||
|
|
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
|
} |
||||
@ -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; |
||||
|
|
||||
|
} |
||||
@ -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 |
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
|
} |
||||
@ -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<TbGpsGeofencingActionNodeConfiguration> { |
||||
|
|
||||
|
private final Map<EntityId, EntityGeofencingState> 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<AttributeKvEntry> 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<AttributeKvEntry> attributeKvEntryList = Collections.singletonList(entry); |
||||
|
ctx.getAttributesService().save(ctx.getTenantId(), entityId, DataConstants.SERVER_SCOPE, attributeKvEntryList); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void destroy() { |
||||
|
|
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
protected Class<TbGpsGeofencingActionNodeConfiguration> getConfigClazz() { |
||||
|
return TbGpsGeofencingActionNodeConfiguration.class; |
||||
|
} |
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
|
} |
||||
@ -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<TbGpsGeofencingFilterNodeConfiguration> { |
||||
|
|
||||
|
@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<TbGpsGeofencingFilterNodeConfiguration> getConfigClazz() { |
||||
|
return TbGpsGeofencingFilterNodeConfiguration.class; |
||||
|
} |
||||
|
} |
||||
@ -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<TbGpsGeofencingFilterNodeConfiguration> { |
||||
|
|
||||
|
private String latitudeKeyName; |
||||
|
private String longitudeKeyName; |
||||
|
private boolean fetchPerimeterInfoFromMessage; |
||||
|
private List<Perimeter> perimeters; |
||||
|
|
||||
|
@Override |
||||
|
public TbGpsGeofencingFilterNodeConfiguration defaultConfiguration() { |
||||
|
TbGpsGeofencingFilterNodeConfiguration configuration = new TbGpsGeofencingFilterNodeConfiguration(); |
||||
|
configuration.setLatitudeKeyName("latitude"); |
||||
|
configuration.setLongitudeKeyName("longitude"); |
||||
|
configuration.setFetchPerimeterInfoFromMessage(true); |
||||
|
configuration.setPerimeters(Collections.emptyList()); |
||||
|
return configuration; |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue