Showing
13 changed files
with
633 additions
and
4 deletions
... | ... | @@ -71,6 +71,8 @@ |
71 | 71 | <jar-plugin.version>3.0.2</jar-plugin.version> |
72 | 72 | <springfox-swagger.version>2.6.1</springfox-swagger.version> |
73 | 73 | <springfox-swagger-ui-rfc6570.version>1.0.0</springfox-swagger-ui-rfc6570.version> |
74 | + <spatial4j.version>0.7</spatial4j.version> | |
75 | + <jts.version>1.15.0</jts.version> | |
74 | 76 | <bouncycastle.version>1.56</bouncycastle.version> |
75 | 77 | <winsw.version>2.0.1</winsw.version> |
76 | 78 | <hsqldb.version>2.4.0</hsqldb.version> |
... | ... | @@ -828,6 +830,16 @@ |
828 | 830 | <artifactId>springfox-swagger-ui-rfc6570</artifactId> |
829 | 831 | <version>${springfox-swagger-ui-rfc6570.version}</version> |
830 | 832 | </dependency> |
833 | + <dependency> | |
834 | + <groupId>org.locationtech.spatial4j</groupId> | |
835 | + <artifactId>spatial4j</artifactId> | |
836 | + <version>${spatial4j.version}</version> | |
837 | + </dependency> | |
838 | + <dependency> | |
839 | + <groupId>org.locationtech.jts</groupId> | |
840 | + <artifactId>jts-core</artifactId> | |
841 | + <version>${jts.version}</version> | |
842 | + </dependency> | |
831 | 843 | </dependencies> |
832 | 844 | </dependencyManagement> |
833 | 845 | ... | ... |
... | ... | @@ -98,6 +98,14 @@ |
98 | 98 | <artifactId>bcpkix-jdk15on</artifactId> |
99 | 99 | </dependency> |
100 | 100 | <dependency> |
101 | + <groupId>org.locationtech.spatial4j</groupId> | |
102 | + <artifactId>spatial4j</artifactId> | |
103 | + </dependency> | |
104 | + <dependency> | |
105 | + <groupId>org.locationtech.jts</groupId> | |
106 | + <artifactId>jts-core</artifactId> | |
107 | + </dependency> | |
108 | + <dependency> | |
101 | 109 | <groupId>junit</groupId> |
102 | 110 | <artifactId>junit</artifactId> |
103 | 111 | <version>${junit.version}</version> |
... | ... | @@ -142,10 +150,10 @@ |
142 | 150 | <executable>true</executable> |
143 | 151 | <excludeDevtools>true</excludeDevtools> |
144 | 152 | <!--<embeddedLaunchScriptProperties>--> |
145 | - <!--<confFolder>${pkg.installFolder}/conf</confFolder>--> | |
146 | - <!--<logFolder>${pkg.unixLogFolder}</logFolder>--> | |
147 | - <!--<logFilename>${pkg.name}.out</logFilename>--> | |
148 | - <!--<initInfoProvides>${pkg.name}</initInfoProvides>--> | |
153 | + <!--<confFolder>${pkg.installFolder}/conf</confFolder>--> | |
154 | + <!--<logFolder>${pkg.unixLogFolder}</logFolder>--> | |
155 | + <!--<logFilename>${pkg.name}.out</logFilename>--> | |
156 | + <!--<initInfoProvides>${pkg.name}</initInfoProvides>--> | |
149 | 157 | <!--</embeddedLaunchScriptProperties>--> |
150 | 158 | </configuration> |
151 | 159 | <executions> | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2019 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.rule.engine.geo; | |
17 | + | |
18 | +import com.google.gson.JsonElement; | |
19 | +import com.google.gson.JsonObject; | |
20 | +import com.google.gson.JsonParser; | |
21 | +import org.locationtech.spatial4j.context.jts.JtsSpatialContext; | |
22 | +import org.locationtech.spatial4j.context.jts.JtsSpatialContextFactory; | |
23 | +import org.springframework.util.StringUtils; | |
24 | +import org.thingsboard.rule.engine.api.TbContext; | |
25 | +import org.thingsboard.rule.engine.api.TbNode; | |
26 | +import org.thingsboard.rule.engine.api.TbNodeConfiguration; | |
27 | +import org.thingsboard.rule.engine.api.TbNodeException; | |
28 | +import org.thingsboard.rule.engine.api.util.TbNodeUtils; | |
29 | +import org.thingsboard.server.common.msg.TbMsg; | |
30 | + | |
31 | +import java.util.Collections; | |
32 | +import java.util.List; | |
33 | + | |
34 | +public abstract class AbstractGeofencingNode<T extends TbGpsGeofencingFilterNodeConfiguration> implements TbNode { | |
35 | + | |
36 | + protected T config; | |
37 | + protected JtsSpatialContext jtsCtx; | |
38 | + | |
39 | + @Override | |
40 | + public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException { | |
41 | + this.config = TbNodeUtils.convert(configuration, getConfigClazz()); | |
42 | + JtsSpatialContextFactory factory = new JtsSpatialContextFactory(); | |
43 | + factory.normWrapLongitude = true; | |
44 | + jtsCtx = factory.newSpatialContext(); | |
45 | + } | |
46 | + | |
47 | + abstract protected Class<T> getConfigClazz(); | |
48 | + | |
49 | + protected boolean checkMatches(TbMsg msg) throws TbNodeException { | |
50 | + JsonElement msgDataElement = new JsonParser().parse(msg.getData()); | |
51 | + if (!msgDataElement.isJsonObject()) { | |
52 | + throw new TbNodeException("Incoming Message is not a valid JSON object"); | |
53 | + } | |
54 | + JsonObject msgDataObj = msgDataElement.getAsJsonObject(); | |
55 | + double latitude = getValueFromMessageByName(msg, msgDataObj, config.getLatitudeKeyName()); | |
56 | + double longitude = getValueFromMessageByName(msg, msgDataObj, config.getLongitudeKeyName()); | |
57 | + List<Perimeter> perimeters = getPerimeters(msg, msgDataObj); | |
58 | + boolean matches = false; | |
59 | + for (Perimeter perimeter : perimeters) { | |
60 | + if (checkMatches(perimeter, latitude, longitude)) { | |
61 | + matches = true; | |
62 | + break; | |
63 | + } | |
64 | + } | |
65 | + return matches; | |
66 | + } | |
67 | + | |
68 | + protected boolean checkMatches(Perimeter perimeter, double latitude, double longitude) throws TbNodeException { | |
69 | + if (perimeter.getPerimeterType() == PerimeterType.CIRCLE) { | |
70 | + Coordinates entityCoordinates = new Coordinates(latitude, longitude); | |
71 | + Coordinates perimeterCoordinates = new Coordinates(perimeter.getCenterLatitude(), perimeter.getCenterLongitude()); | |
72 | + return perimeter.getRange() > GeoUtil.distance(entityCoordinates, perimeterCoordinates, perimeter.getRangeUnit()); | |
73 | + } else if (perimeter.getPerimeterType() == PerimeterType.POLYGON) { | |
74 | + return GeoUtil.contains(perimeter.getPolygonsDefinition(), new Coordinates(latitude, longitude)); | |
75 | + } else { | |
76 | + throw new TbNodeException("Unsupported perimeter type: " + perimeter.getPerimeterType()); | |
77 | + } | |
78 | + } | |
79 | + | |
80 | + protected List<Perimeter> getPerimeters(TbMsg msg, JsonObject msgDataObj) throws TbNodeException { | |
81 | + if (config.isFetchPerimeterInfoFromMessage()) { | |
82 | + //TODO: add fetching perimeters from the message itself, if configuration is empty. | |
83 | + if (!StringUtils.isEmpty(msg.getMetaData().getValue("perimeter"))) { | |
84 | + Perimeter perimeter = new Perimeter(); | |
85 | + perimeter.setPerimeterType(PerimeterType.POLYGON); | |
86 | + perimeter.setPolygonsDefinition(msg.getMetaData().getValue("perimeter")); | |
87 | + return Collections.singletonList(perimeter); | |
88 | + } else if (!StringUtils.isEmpty(msg.getMetaData().getValue("centerLatitude"))) { | |
89 | + Perimeter perimeter = new Perimeter(); | |
90 | + perimeter.setPerimeterType(PerimeterType.CIRCLE); | |
91 | + perimeter.setCenterLatitude(Double.parseDouble(msg.getMetaData().getValue("centerLatitude"))); | |
92 | + perimeter.setCenterLongitude(Double.parseDouble(msg.getMetaData().getValue("centerLongitude"))); | |
93 | + perimeter.setRange(Double.parseDouble(msg.getMetaData().getValue("range"))); | |
94 | + perimeter.setRangeUnit(RangeUnit.valueOf(msg.getMetaData().getValue("rangeUnit"))); | |
95 | + return Collections.singletonList(perimeter); | |
96 | + } else { | |
97 | + throw new TbNodeException("Missing perimeter definition!"); | |
98 | + } | |
99 | + } else { | |
100 | + return config.getPerimeters(); | |
101 | + } | |
102 | + } | |
103 | + | |
104 | + protected Double getValueFromMessageByName(TbMsg msg, JsonObject msgDataObj, String keyName) throws TbNodeException { | |
105 | + double value; | |
106 | + if (msgDataObj.has(keyName) && msgDataObj.get(keyName).isJsonPrimitive()) { | |
107 | + value = msgDataObj.get(keyName).getAsDouble(); | |
108 | + } else { | |
109 | + String valueStr = msg.getMetaData().getValue(keyName); | |
110 | + if (!StringUtils.isEmpty(valueStr)) { | |
111 | + value = Double.parseDouble(valueStr); | |
112 | + } else { | |
113 | + throw new TbNodeException("Incoming Message has no " + keyName + " in data or metadata!"); | |
114 | + } | |
115 | + } | |
116 | + return value; | |
117 | + } | |
118 | + | |
119 | + | |
120 | +} | ... | ... |
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/Coordinates.java
0 → 100644
1 | +/** | |
2 | + * Copyright © 2016-2019 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.rule.engine.geo; | |
17 | + | |
18 | +import lombok.Data; | |
19 | + | |
20 | +@Data | |
21 | +public class Coordinates { | |
22 | + private final double latitude; | |
23 | + private final double longitude; | |
24 | +} | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2019 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.rule.engine.geo; | |
17 | + | |
18 | +import lombok.AllArgsConstructor; | |
19 | +import lombok.Data; | |
20 | + | |
21 | +@Data | |
22 | +@AllArgsConstructor | |
23 | +public class EntityGeofencingState { | |
24 | + | |
25 | + private boolean inside; | |
26 | + private long stateSwitchTime; | |
27 | + private boolean staid; | |
28 | + | |
29 | +} | ... | ... |
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/GeoUtil.java
0 → 100644
1 | +/** | |
2 | + * Copyright © 2016-2019 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.rule.engine.geo; | |
17 | + | |
18 | +import com.google.gson.JsonArray; | |
19 | +import com.google.gson.JsonElement; | |
20 | +import com.google.gson.JsonParser; | |
21 | +import org.locationtech.spatial4j.context.SpatialContext; | |
22 | +import org.locationtech.spatial4j.context.jts.JtsSpatialContext; | |
23 | +import org.locationtech.spatial4j.context.jts.JtsSpatialContextFactory; | |
24 | +import org.locationtech.spatial4j.distance.DistanceUtils; | |
25 | +import org.locationtech.spatial4j.shape.Point; | |
26 | +import org.locationtech.spatial4j.shape.Shape; | |
27 | +import org.locationtech.spatial4j.shape.ShapeFactory; | |
28 | +import org.locationtech.spatial4j.shape.SpatialRelation; | |
29 | + | |
30 | +public class GeoUtil { | |
31 | + | |
32 | + private static final SpatialContext distCtx = SpatialContext.GEO; | |
33 | + private static final JtsSpatialContext jtsCtx; | |
34 | + | |
35 | + static { | |
36 | + JtsSpatialContextFactory factory = new JtsSpatialContextFactory(); | |
37 | + factory.normWrapLongitude = true; | |
38 | + jtsCtx = factory.newSpatialContext(); | |
39 | + } | |
40 | + | |
41 | + public static synchronized double distance(Coordinates x, Coordinates y, RangeUnit unit) { | |
42 | + Point xLL = distCtx.getShapeFactory().pointXY(x.getLongitude(), x.getLatitude()); | |
43 | + Point yLL = distCtx.getShapeFactory().pointXY(y.getLongitude(), y.getLatitude()); | |
44 | + return unit.fromKm(distCtx.getDistCalc().distance(xLL, yLL) * DistanceUtils.DEG_TO_KM); | |
45 | + } | |
46 | + | |
47 | + public static synchronized boolean contains(String polygon, Coordinates coordinates) { | |
48 | + ShapeFactory.PolygonBuilder polygonBuilder = jtsCtx.getShapeFactory().polygon(); | |
49 | + JsonArray polygonArray = new JsonParser().parse(polygon).getAsJsonArray(); | |
50 | + boolean first = true; | |
51 | + double firstLat = 0.0; | |
52 | + double firstLng = 0.0; | |
53 | + for (JsonElement jsonElement : polygonArray) { | |
54 | + double lat = jsonElement.getAsJsonArray().get(0).getAsDouble(); | |
55 | + double lng = jsonElement.getAsJsonArray().get(1).getAsDouble(); | |
56 | + if (first) { | |
57 | + firstLat = lat; | |
58 | + firstLng = lng; | |
59 | + first = false; | |
60 | + } | |
61 | + polygonBuilder.pointXY(jtsCtx.getShapeFactory().normX(lng), jtsCtx.getShapeFactory().normY(lat)); | |
62 | + } | |
63 | + polygonBuilder.pointXY(jtsCtx.getShapeFactory().normX(firstLng), jtsCtx.getShapeFactory().normY(firstLat)); | |
64 | + Shape shape = polygonBuilder.buildOrRect(); | |
65 | + Point point = jtsCtx.makePoint(coordinates.getLongitude(), coordinates.getLatitude()); | |
66 | + return shape.relate(point).equals(SpatialRelation.CONTAINS); | |
67 | + } | |
68 | +} | ... | ... |
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/Perimeter.java
0 → 100644
1 | +/** | |
2 | + * Copyright © 2016-2019 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.rule.engine.geo; | |
17 | + | |
18 | +import lombok.Data; | |
19 | + | |
20 | +@Data | |
21 | +public class Perimeter { | |
22 | + | |
23 | + private PerimeterType perimeterType; | |
24 | + | |
25 | + //For Polygons | |
26 | + private String polygonsDefinition; | |
27 | + | |
28 | + //For Circles | |
29 | + private Double centerLatitude; | |
30 | + private Double centerLongitude; | |
31 | + private Double range; | |
32 | + private RangeUnit rangeUnit; | |
33 | + | |
34 | +} | ... | ... |
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/PerimeterType.java
0 → 100644
1 | +/** | |
2 | + * Copyright © 2016-2019 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.rule.engine.geo; | |
17 | + | |
18 | +public enum PerimeterType { | |
19 | + CIRCLE, POLYGON | |
20 | +} | ... | ... |
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/RangeUnit.java
0 → 100644
1 | +/** | |
2 | + * Copyright © 2016-2019 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.rule.engine.geo; | |
17 | + | |
18 | +public enum RangeUnit { | |
19 | + METER(1000.0), KILOMETER(1.0), FOOT(3280.84), MILE(0.62137), NAUTICAL_MILE(0.539957); | |
20 | + | |
21 | + private final double fromKm; | |
22 | + | |
23 | + RangeUnit(double fromKm) { | |
24 | + this.fromKm = fromKm; | |
25 | + } | |
26 | + | |
27 | + public double fromKm(double v) { | |
28 | + return v * fromKm; | |
29 | + } | |
30 | +} | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2019 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.rule.engine.geo; | |
17 | + | |
18 | +import com.google.gson.Gson; | |
19 | +import com.google.gson.JsonObject; | |
20 | +import com.google.gson.JsonParser; | |
21 | +import lombok.extern.slf4j.Slf4j; | |
22 | +import org.thingsboard.rule.engine.api.RuleNode; | |
23 | +import org.thingsboard.rule.engine.api.TbContext; | |
24 | +import org.thingsboard.rule.engine.api.TbNodeException; | |
25 | +import org.thingsboard.server.common.data.DataConstants; | |
26 | +import org.thingsboard.server.common.data.id.EntityId; | |
27 | +import org.thingsboard.server.common.data.kv.AttributeKvEntry; | |
28 | +import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; | |
29 | +import org.thingsboard.server.common.data.kv.StringDataEntry; | |
30 | +import org.thingsboard.server.common.data.plugin.ComponentType; | |
31 | +import org.thingsboard.server.common.msg.TbMsg; | |
32 | + | |
33 | +import java.util.Collections; | |
34 | +import java.util.HashMap; | |
35 | +import java.util.List; | |
36 | +import java.util.Map; | |
37 | +import java.util.Optional; | |
38 | +import java.util.concurrent.ExecutionException; | |
39 | +import java.util.concurrent.TimeUnit; | |
40 | +import java.util.concurrent.TimeoutException; | |
41 | + | |
42 | +/** | |
43 | + * Created by ashvayka on 19.01.18. | |
44 | + */ | |
45 | +@Slf4j | |
46 | +@RuleNode( | |
47 | + type = ComponentType.ACTION, | |
48 | + name = "gps geofencing events", | |
49 | + configClazz = TbGpsGeofencingFilterNodeConfiguration.class, | |
50 | + relationTypes = {"Entered", "Left", "Stays Inside", "Stays Outside"}, | |
51 | + nodeDescription = "Produces incoming messages using GPS based geofencing", | |
52 | + nodeDetails = "Extracts latitude and longitude parameters from incoming message and returns different events based on configuration parameters") | |
53 | +public class TbGpsGeofencingActionNode extends AbstractGeofencingNode<TbGpsGeofencingActionNodeConfiguration> { | |
54 | + | |
55 | + private final Map<EntityId, EntityGeofencingState> entityStates = new HashMap<>(); | |
56 | + private final Gson gson = new Gson(); | |
57 | + private final JsonParser parser = new JsonParser(); | |
58 | + | |
59 | + @Override | |
60 | + public void onMsg(TbContext ctx, TbMsg msg) throws TbNodeException { | |
61 | + boolean matches = checkMatches(msg); | |
62 | + long ts = System.currentTimeMillis(); | |
63 | + | |
64 | + EntityGeofencingState entityState = entityStates.computeIfAbsent(msg.getOriginator(), key -> { | |
65 | + try { | |
66 | + Optional<AttributeKvEntry> entry = ctx.getAttributesService() | |
67 | + .find(ctx.getTenantId(), msg.getOriginator(), DataConstants.SERVER_SCOPE, ctx.getNodeId()) | |
68 | + .get(1, TimeUnit.MINUTES); | |
69 | + if (entry.isPresent()) { | |
70 | + JsonObject element = parser.parse(entry.get().getValueAsString()).getAsJsonObject(); | |
71 | + return new EntityGeofencingState(element.get("inside").getAsBoolean(), element.get("stateSwitchTime").getAsLong(), element.get("staid").getAsBoolean()); | |
72 | + } else { | |
73 | + return new EntityGeofencingState(false, 0L, false); | |
74 | + } | |
75 | + } catch (InterruptedException | TimeoutException | ExecutionException e) { | |
76 | + throw new RuntimeException(e); | |
77 | + } | |
78 | + }); | |
79 | + if (entityState.getStateSwitchTime() == 0L || entityState.isInside() != matches) { | |
80 | + switchState(ctx, msg.getOriginator(), entityState, matches, ts); | |
81 | + ctx.tellNext(msg, matches ? "Entered" : "Left"); | |
82 | + } else if (!entityState.isStaid()) { | |
83 | + long stayTime = ts - entityState.getStateSwitchTime(); | |
84 | + if (stayTime > (entityState.isInside() ? config.getMinInsideDuration() : config.getMinOutsideDuration())) { | |
85 | + setStaid(ctx, msg.getOriginator(), entityState); | |
86 | + } | |
87 | + } | |
88 | + } | |
89 | + | |
90 | + private void switchState(TbContext ctx, EntityId entityId, EntityGeofencingState entityState, boolean matches, long ts) { | |
91 | + entityState.setInside(matches); | |
92 | + entityState.setStateSwitchTime(ts); | |
93 | + entityState.setStaid(false); | |
94 | + persist(ctx, entityId, entityState); | |
95 | + } | |
96 | + | |
97 | + private void setStaid(TbContext ctx, EntityId entityId, EntityGeofencingState entityState) { | |
98 | + entityState.setStaid(true); | |
99 | + persist(ctx, entityId, entityState); | |
100 | + } | |
101 | + | |
102 | + private void persist(TbContext ctx, EntityId entityId, EntityGeofencingState entityState) { | |
103 | + JsonObject object = new JsonObject(); | |
104 | + object.addProperty("inside", entityState.isInside()); | |
105 | + object.addProperty("stateSwitchTime", entityState.getStateSwitchTime()); | |
106 | + object.addProperty("staid", entityState.isStaid()); | |
107 | + AttributeKvEntry entry = new BaseAttributeKvEntry(new StringDataEntry(ctx.getNodeId(), gson.toJson(object)), System.currentTimeMillis()); | |
108 | + List<AttributeKvEntry> attributeKvEntryList = Collections.singletonList(entry); | |
109 | + ctx.getAttributesService().save(ctx.getTenantId(), entityId, DataConstants.SERVER_SCOPE, attributeKvEntryList); | |
110 | + } | |
111 | + | |
112 | + @Override | |
113 | + public void destroy() { | |
114 | + | |
115 | + } | |
116 | + | |
117 | + @Override | |
118 | + protected Class<TbGpsGeofencingActionNodeConfiguration> getConfigClazz() { | |
119 | + return TbGpsGeofencingActionNodeConfiguration.class; | |
120 | + } | |
121 | +} | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2019 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.rule.engine.geo; | |
17 | + | |
18 | +import lombok.Data; | |
19 | +import org.thingsboard.rule.engine.api.NodeConfiguration; | |
20 | + | |
21 | +import java.util.Collections; | |
22 | +import java.util.List; | |
23 | +import java.util.concurrent.TimeUnit; | |
24 | + | |
25 | +/** | |
26 | + * Created by ashvayka on 19.01.18. | |
27 | + */ | |
28 | +@Data | |
29 | +public class TbGpsGeofencingActionNodeConfiguration extends TbGpsGeofencingFilterNodeConfiguration { | |
30 | + | |
31 | + private double minInsideDuration; | |
32 | + private double minOutsideDuration; | |
33 | + | |
34 | + @Override | |
35 | + public TbGpsGeofencingActionNodeConfiguration defaultConfiguration() { | |
36 | + TbGpsGeofencingActionNodeConfiguration configuration = new TbGpsGeofencingActionNodeConfiguration(); | |
37 | + configuration.setLatitudeKeyName("latitude"); | |
38 | + configuration.setLongitudeKeyName("longitude"); | |
39 | + configuration.setFetchPerimeterInfoFromMessage(true); | |
40 | + configuration.setPerimeters(Collections.emptyList()); | |
41 | + configuration.setMinInsideDuration(TimeUnit.MINUTES.toMillis(1)); | |
42 | + configuration.setMinOutsideDuration(TimeUnit.MINUTES.toMillis(1)); | |
43 | + return configuration; | |
44 | + } | |
45 | +} | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2019 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.rule.engine.geo; | |
17 | + | |
18 | +import com.google.gson.JsonArray; | |
19 | +import com.google.gson.JsonElement; | |
20 | +import com.google.gson.JsonObject; | |
21 | +import com.google.gson.JsonParser; | |
22 | +import lombok.extern.slf4j.Slf4j; | |
23 | +import org.locationtech.spatial4j.context.jts.JtsSpatialContext; | |
24 | +import org.locationtech.spatial4j.context.jts.JtsSpatialContextFactory; | |
25 | +import org.locationtech.spatial4j.shape.Point; | |
26 | +import org.locationtech.spatial4j.shape.Shape; | |
27 | +import org.locationtech.spatial4j.shape.ShapeFactory; | |
28 | +import org.locationtech.spatial4j.shape.SpatialRelation; | |
29 | +import org.springframework.util.StringUtils; | |
30 | +import org.thingsboard.rule.engine.api.RuleNode; | |
31 | +import org.thingsboard.rule.engine.api.TbContext; | |
32 | +import org.thingsboard.rule.engine.api.TbNode; | |
33 | +import org.thingsboard.rule.engine.api.TbNodeConfiguration; | |
34 | +import org.thingsboard.rule.engine.api.TbNodeException; | |
35 | +import org.thingsboard.rule.engine.api.util.TbNodeUtils; | |
36 | +import org.thingsboard.rule.engine.filter.TbMsgTypeFilterNodeConfiguration; | |
37 | +import org.thingsboard.server.common.data.plugin.ComponentType; | |
38 | +import org.thingsboard.server.common.msg.TbMsg; | |
39 | + | |
40 | +import java.util.Collections; | |
41 | +import java.util.List; | |
42 | + | |
43 | +/** | |
44 | + * Created by ashvayka on 19.01.18. | |
45 | + */ | |
46 | +@Slf4j | |
47 | +@RuleNode( | |
48 | + type = ComponentType.FILTER, | |
49 | + name = "gps geofencing filter", | |
50 | + configClazz = TbGpsGeofencingFilterNodeConfiguration.class, | |
51 | + relationTypes = {"True", "False"}, | |
52 | + nodeDescription = "Filter incoming messages by GPS based geofencing", | |
53 | + nodeDetails = "Extracts latitude and longitude parameters from incoming message and returns 'True' if they are inside configured perimeters, 'False' otherwise.") | |
54 | +public class TbGpsGeofencingFilterNode extends AbstractGeofencingNode<TbGpsGeofencingFilterNodeConfiguration> { | |
55 | + | |
56 | + @Override | |
57 | + public void onMsg(TbContext ctx, TbMsg msg) throws TbNodeException { | |
58 | + boolean matches = checkMatches(msg); | |
59 | + ctx.tellNext(msg, matches ? "True" : "False"); | |
60 | + } | |
61 | + | |
62 | + @Override | |
63 | + public void destroy() { | |
64 | + | |
65 | + } | |
66 | + | |
67 | + @Override | |
68 | + protected Class<TbGpsGeofencingFilterNodeConfiguration> getConfigClazz() { | |
69 | + return TbGpsGeofencingFilterNodeConfiguration.class; | |
70 | + } | |
71 | +} | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2019 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.rule.engine.geo; | |
17 | + | |
18 | +import lombok.Data; | |
19 | +import org.thingsboard.rule.engine.api.NodeConfiguration; | |
20 | +import org.thingsboard.server.common.data.EntityType; | |
21 | +import org.thingsboard.server.common.msg.session.SessionMsgType; | |
22 | + | |
23 | +import java.util.Arrays; | |
24 | +import java.util.Collections; | |
25 | +import java.util.List; | |
26 | + | |
27 | +/** | |
28 | + * Created by ashvayka on 19.01.18. | |
29 | + */ | |
30 | +@Data | |
31 | +public class TbGpsGeofencingFilterNodeConfiguration implements NodeConfiguration<TbGpsGeofencingFilterNodeConfiguration> { | |
32 | + | |
33 | + private String latitudeKeyName; | |
34 | + private String longitudeKeyName; | |
35 | + private boolean fetchPerimeterInfoFromMessage; | |
36 | + private List<Perimeter> perimeters; | |
37 | + | |
38 | + @Override | |
39 | + public TbGpsGeofencingFilterNodeConfiguration defaultConfiguration() { | |
40 | + TbGpsGeofencingFilterNodeConfiguration configuration = new TbGpsGeofencingFilterNodeConfiguration(); | |
41 | + configuration.setLatitudeKeyName("latitude"); | |
42 | + configuration.setLongitudeKeyName("longitude"); | |
43 | + configuration.setFetchPerimeterInfoFromMessage(true); | |
44 | + configuration.setPerimeters(Collections.emptyList()); | |
45 | + return configuration; | |
46 | + } | |
47 | +} | ... | ... |