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