aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAnton Tananaev <anton.tananaev@gmail.com>2016-06-18 11:56:59 +1200
committerGitHub <noreply@github.com>2016-06-18 11:56:59 +1200
commitb3b0cd9d6f2d26cef2e64ef38e23203f1f3fa51a (patch)
tree2e8deb61d372095aebb8270f740501c8124ea8c4
parentd801cba474cd05bb088348f04e8557ca638cd74f (diff)
parent82a78ff77a076231a8429f0dd84678d61c31d44a (diff)
downloadtraccar-server-b3b0cd9d6f2d26cef2e64ef38e23203f1f3fa51a.tar.gz
traccar-server-b3b0cd9d6f2d26cef2e64ef38e23203f1f3fa51a.tar.bz2
traccar-server-b3b0cd9d6f2d26cef2e64ef38e23203f1f3fa51a.zip
Merge pull request #2012 from Abyss777/master
Implement Geofences on server side
-rw-r--r--debug.xml88
-rw-r--r--schema/changelog-3.6.xml53
-rw-r--r--setup/unix/traccar.xml69
-rw-r--r--setup/windows/traccar.xml69
-rw-r--r--src/org/traccar/BaseEventHandler.java40
-rw-r--r--src/org/traccar/BasePipelineFactory.java10
-rw-r--r--src/org/traccar/Context.java21
-rw-r--r--src/org/traccar/api/resource/DeviceGeofenceResource.java57
-rw-r--r--src/org/traccar/api/resource/DevicePermissionResource.java2
-rw-r--r--src/org/traccar/api/resource/DeviceResource.java6
-rw-r--r--src/org/traccar/api/resource/GeofencePermissionResource.java56
-rw-r--r--src/org/traccar/api/resource/GeofenceResource.java85
-rw-r--r--src/org/traccar/api/resource/GroupGeofenceResource.java56
-rw-r--r--src/org/traccar/api/resource/GroupPermissionResource.java2
-rw-r--r--src/org/traccar/api/resource/GroupResource.java2
-rw-r--r--src/org/traccar/api/resource/UserResource.java5
-rw-r--r--src/org/traccar/database/ConnectionManager.java20
-rw-r--r--src/org/traccar/database/DataManager.java126
-rw-r--r--src/org/traccar/database/GeofenceManager.java227
-rw-r--r--src/org/traccar/database/NotificationManager.java57
-rw-r--r--src/org/traccar/database/PermissionsManager.java9
-rw-r--r--src/org/traccar/events/CommandResultEventHandler.java24
-rw-r--r--src/org/traccar/events/GeofenceEventHandler.java92
-rw-r--r--src/org/traccar/events/MotionEventHandler.java55
-rw-r--r--src/org/traccar/events/OverspeedEventHandler.java36
-rw-r--r--src/org/traccar/geofence/GeofenceCircle.java94
-rw-r--r--src/org/traccar/geofence/GeofenceGeometry.java28
-rw-r--r--src/org/traccar/geofence/GeofencePolygon.java173
-rw-r--r--src/org/traccar/model/Device.java10
-rw-r--r--src/org/traccar/model/DeviceGeofence.java40
-rw-r--r--src/org/traccar/model/Event.java35
-rw-r--r--src/org/traccar/model/Extensible.java71
-rw-r--r--src/org/traccar/model/Geofence.java82
-rw-r--r--src/org/traccar/model/GeofencePermission.java40
-rw-r--r--src/org/traccar/model/GroupGeofence.java40
-rw-r--r--src/org/traccar/model/Message.java45
-rw-r--r--src/org/traccar/model/Position.java12
-rw-r--r--src/org/traccar/web/WebServer.java7
-rw-r--r--test/org/traccar/geofence/GeofenceCircleTest.java36
-rw-r--r--test/org/traccar/geofence/GeofencePolygonTest.java38
-rw-r--r--web/l10n/en.json2
41 files changed, 1886 insertions, 134 deletions
diff --git a/debug.xml b/debug.xml
index b16a596bf..f04ceb271 100644
--- a/debug.xml
+++ b/debug.xml
@@ -41,6 +41,16 @@
<entry key='logger.level'>all</entry>
<entry key='logger.file'>target/tracker-server.log</entry>
+ <entry key='event.suppressRepeated'>60</entry>
+
+ <entry key='event.overspeedHandler'>true</entry>
+ <entry key='event.globalSpeedLimit'>90</entry>
+
+ <entry key='event.motionHandler'>true</entry>
+
+
+ <entry key='event.geofenceHandler'>true</entry>
+
<!-- DATABASE CONFIG -->
<!--<entry key='database.driverFile'>hsqldb.jar</entry>-->
@@ -137,7 +147,7 @@
</entry>
<entry key='database.updateDeviceStatus'>
- UPDATE devices SET status = :status, lastUpdate = :lastUpdate WHERE id = :id;
+ UPDATE devices SET status = :status, lastUpdate = :lastUpdate, motion = :motion WHERE id = :id;
</entry>
<entry key='database.deleteDevice'>
@@ -193,6 +203,82 @@
UPDATE devices SET positionId = :id WHERE id = :deviceId;
</entry>
+ <entry key='database.selectEvent'>
+ SELECT * FROM events WHERE id = :id;
+ </entry>
+
+ <entry key='database.insertEvent'>
+ INSERT INTO events (type, serverTime, deviceId, positionId, geofenceId, attributes)
+ VALUES (:type, :serverTime, :deviceId, :positionId, :geofenceId, :attributes);
+ </entry>
+
+ <entry key='database.selectEvents'>
+ SELECT * FROM events WHERE deviceId = :deviceId AND type LIKE :type AND serverTime BETWEEN :from AND :to ORDER BY serverTime DESC;
+ </entry>
+
+ <entry key='database.selectGeofence'>
+ SELECT * FROM geofences
+ WHERE id = :id;
+ </entry>
+
+ <entry key='database.selectGeofencesAll'>
+ SELECT * FROM geofences;
+ </entry>
+
+ <entry key='database.insertGeofence'>
+ INSERT INTO geofences (name, description, area, attributes)
+ VALUES (:name, :description, :area, :attributes);
+ </entry>
+
+ <entry key='database.updateGeofence'>
+ UPDATE geofences SET
+ name = :name,
+ description = :description,
+ area = :area,
+ attributes = :attributes
+ WHERE id = :id;
+ </entry>
+
+ <entry key='database.deleteGeofence'>
+ DELETE FROM geofences WHERE id = :id;
+ </entry>
+
+ <entry key='database.selectGeofencePermissions'>
+ SELECT userId, geofenceId FROM user_geofence;
+ </entry>
+
+ <entry key='database.linkGeofence'>
+ INSERT INTO user_geofence (userId, geofenceId) VALUES (:userId, :geofenceId);
+ </entry>
+
+ <entry key='database.unlinkGeofence'>
+ DELETE FROM user_geofence WHERE userId = :userId AND geofenceId = :geofenceId;
+ </entry>
+
+ <entry key='database.selectGroupGeofences'>
+ SELECT groupId, geofenceId FROM group_geofence;
+ </entry>
+
+ <entry key='database.linkGroupGeofence'>
+ INSERT INTO group_geofence (groupId, geofenceId) VALUES (:groupId, :geofenceId);
+ </entry>
+
+ <entry key='database.unlinkGroupGeofence'>
+ DELETE FROM group_geofence WHERE groupId = :groupId AND geofenceId = :geofenceId;
+ </entry>
+
+ <entry key='database.selectDeviceGeofences'>
+ SELECT deviceId, geofenceId FROM device_geofence;
+ </entry>
+
+ <entry key='database.linkDeviceGeofence'>
+ INSERT INTO device_geofence (deviceId, geofenceId) VALUES (:deviceId, :geofenceId);
+ </entry>
+
+ <entry key='database.unlinkDeviceGeofence'>
+ DELETE FROM device_geofence WHERE deviceId = :deviceId AND geofenceId = :geofenceId;
+ </entry>
+
<!-- PROTOCOL CONFIG -->
<entry key='gps103.port'>5001</entry>
diff --git a/schema/changelog-3.6.xml b/schema/changelog-3.6.xml
index e2fd72a61..13d4cae60 100644
--- a/schema/changelog-3.6.xml
+++ b/schema/changelog-3.6.xml
@@ -26,6 +26,7 @@
</column>
<column name="deviceid" type="INT" />
<column name="positionid" type="INT" />
+ <column name="geofenceid" type="INT" />
<column name="attributes" type="VARCHAR(4096)">
<constraints nullable="false" />
</column>
@@ -37,5 +38,57 @@
<column name="motion" type="VARCHAR(128)" />
</addColumn>
+ <createTable tableName="geofences">
+ <column name="id" type="INT" autoIncrement="true">
+ <constraints primaryKey="true" />
+ </column>
+ <column name="name" type="VARCHAR(128)">
+ <constraints nullable="false" />
+ </column>
+ <column name="description" type="VARCHAR(128)" />
+ <column name="area" type="VARCHAR(4096)">
+ <constraints nullable="false" />
+ </column>
+ <column name="attributes" type="VARCHAR(4096)">
+ <constraints nullable="false" />
+ </column>
+ </createTable>
+
+ <createTable tableName="user_geofence">
+ <column name="userid" type="INT">
+ <constraints nullable="false" />
+ </column>
+ <column name="geofenceid" type="INT">
+ <constraints nullable="false" />
+ </column>
+ </createTable>
+
+ <addForeignKeyConstraint baseTableName="user_geofence" baseColumnNames="userid" constraintName="fk_user_geofence_userid" referencedTableName="users" referencedColumnNames="id" onDelete="CASCADE" />
+ <addForeignKeyConstraint baseTableName="user_geofence" baseColumnNames="geofenceid" constraintName="fk_user_geofence_geofenceid" referencedTableName="geofences" referencedColumnNames="id" onDelete="CASCADE" />
+
+ <createTable tableName="group_geofence">
+ <column name="groupid" type="INT">
+ <constraints nullable="false" />
+ </column>
+ <column name="geofenceid" type="INT">
+ <constraints nullable="false" />
+ </column>
+ </createTable>
+
+ <addForeignKeyConstraint baseTableName="group_geofence" baseColumnNames="groupid" constraintName="fk_group_geofence_groupid" referencedTableName="groups" referencedColumnNames="id" onDelete="CASCADE" />
+ <addForeignKeyConstraint baseTableName="group_geofence" baseColumnNames="geofenceid" constraintName="fk_group_geofence_geofenceid" referencedTableName="geofences" referencedColumnNames="id" onDelete="CASCADE" />
+
+ <createTable tableName="device_geofence">
+ <column name="deviceid" type="INT">
+ <constraints nullable="false" />
+ </column>
+ <column name="geofenceid" type="INT">
+ <constraints nullable="false" />
+ </column>
+ </createTable>
+
+ <addForeignKeyConstraint baseTableName="device_geofence" baseColumnNames="deviceid" constraintName="fk_user_device_geofence_deviceid" referencedTableName="devices" referencedColumnNames="id" onDelete="CASCADE" />
+ <addForeignKeyConstraint baseTableName="device_geofence" baseColumnNames="geofenceid" constraintName="fk_user_device_geofence_geofenceid" referencedTableName="geofences" referencedColumnNames="id" onDelete="CASCADE" />
+
</changeSet>
</databaseChangeLog>
diff --git a/setup/unix/traccar.xml b/setup/unix/traccar.xml
index d8d4a1084..ab739e4d6 100644
--- a/setup/unix/traccar.xml
+++ b/setup/unix/traccar.xml
@@ -24,6 +24,8 @@
<entry key='event.motionHandler'>true</entry>
+ <entry key='event.geofenceHandler'>true</entry>
+
<!-- DATABASE CONFIG -->
<entry key='database.driver'>org.h2.Driver</entry>
@@ -177,14 +179,77 @@
</entry>
<entry key='database.insertEvent'>
- INSERT INTO events (type, serverTime, deviceId, positionId, attributes)
- VALUES (:type, :serverTime, :deviceId, :positionId, :attributes);
+ INSERT INTO events (type, serverTime, deviceId, positionId, geofenceId, attributes)
+ VALUES (:type, :serverTime, :deviceId, :positionId, :geofenceId, :attributes);
</entry>
<entry key='database.selectEvents'>
SELECT * FROM events WHERE deviceId = :deviceId AND type LIKE :type AND serverTime BETWEEN :from AND :to ORDER BY serverTime DESC;
</entry>
+ <entry key='database.selectGeofence'>
+ SELECT * FROM geofences
+ WHERE id = :id;
+ </entry>
+
+ <entry key='database.selectGeofencesAll'>
+ SELECT * FROM geofences;
+ </entry>
+
+ <entry key='database.insertGeofence'>
+ INSERT INTO geofences (name, description, area, attributes)
+ VALUES (:name, :description, :area, :attributes);
+ </entry>
+
+ <entry key='database.updateGeofence'>
+ UPDATE geofences SET
+ name = :name,
+ description = :description,
+ area = :area,
+ attributes = :attributes
+ WHERE id = :id;
+ </entry>
+
+ <entry key='database.deleteGeofence'>
+ DELETE FROM geofences WHERE id = :id;
+ </entry>
+
+ <entry key='database.selectGeofencePermissions'>
+ SELECT userId, geofenceId FROM user_geofence;
+ </entry>
+
+ <entry key='database.linkGeofence'>
+ INSERT INTO user_geofence (userId, geofenceId) VALUES (:userId, :geofenceId);
+ </entry>
+
+ <entry key='database.unlinkGeofence'>
+ DELETE FROM user_geofence WHERE userId = :userId AND geofenceId = :geofenceId;
+ </entry>
+
+ <entry key='database.selectGroupGeofences'>
+ SELECT groupId, geofenceId FROM group_geofence;
+ </entry>
+
+ <entry key='database.linkGroupGeofence'>
+ INSERT INTO group_geofence (groupId, geofenceId) VALUES (:groupId, :geofenceId);
+ </entry>
+
+ <entry key='database.unlinkGroupGeofence'>
+ DELETE FROM group_geofence WHERE groupId = :groupId AND geofenceId = :geofenceId;
+ </entry>
+
+ <entry key='database.selectDeviceGeofences'>
+ SELECT deviceId, geofenceId FROM device_geofence;
+ </entry>
+
+ <entry key='database.linkDeviceGeofence'>
+ INSERT INTO device_geofence (deviceId, geofenceId) VALUES (:deviceId, :geofenceId);
+ </entry>
+
+ <entry key='database.unlinkDeviceGeofence'>
+ DELETE FROM device_geofence WHERE deviceId = :deviceId AND geofenceId = :geofenceId;
+ </entry>
+
<!-- PROTOCOL CONFIG -->
<entry key='gps103.port'>5001</entry>
diff --git a/setup/windows/traccar.xml b/setup/windows/traccar.xml
index 71f56f4c7..17f2ab4f3 100644
--- a/setup/windows/traccar.xml
+++ b/setup/windows/traccar.xml
@@ -24,6 +24,8 @@
<entry key='event.motionHandler'>true</entry>
+ <entry key='event.geofenceHandler'>true</entry>
+
<!-- DATABASE CONFIG -->
<entry key='database.driver'>org.h2.Driver</entry>
@@ -177,14 +179,77 @@
</entry>
<entry key='database.insertEvent'>
- INSERT INTO events (type, serverTime, deviceId, positionId, attributes)
- VALUES (:type, :serverTime, :deviceId, :positionId, :attributes);
+ INSERT INTO events (type, serverTime, deviceId, positionId, geofenceId, attributes)
+ VALUES (:type, :serverTime, :deviceId, :positionId, :geofenceId, :attributes);
</entry>
<entry key='database.selectEvents'>
SELECT * FROM events WHERE deviceId = :deviceId AND type LIKE :type AND serverTime BETWEEN :from AND :to ORDER BY serverTime DESC;
</entry>
+ <entry key='database.selectGeofence'>
+ SELECT * FROM geofences
+ WHERE id = :id;
+ </entry>
+
+ <entry key='database.selectGeofencesAll'>
+ SELECT * FROM geofences;
+ </entry>
+
+ <entry key='database.insertGeofence'>
+ INSERT INTO geofences (name, description, area, attributes)
+ VALUES (:name, :description, :area, :attributes);
+ </entry>
+
+ <entry key='database.updateGeofence'>
+ UPDATE geofences SET
+ name = :name,
+ description = :description,
+ area = :area,
+ attributes = :attributes
+ WHERE id = :id;
+ </entry>
+
+ <entry key='database.deleteGeofence'>
+ DELETE FROM geofences WHERE id = :id;
+ </entry>
+
+ <entry key='database.selectGeofencePermissions'>
+ SELECT userId, geofenceId FROM user_geofence;
+ </entry>
+
+ <entry key='database.linkGeofence'>
+ INSERT INTO user_geofence (userId, geofenceId) VALUES (:userId, :geofenceId);
+ </entry>
+
+ <entry key='database.unlinkGeofence'>
+ DELETE FROM user_geofence WHERE userId = :userId AND geofenceId = :geofenceId;
+ </entry>
+
+ <entry key='database.selectGroupGeofences'>
+ SELECT groupId, geofenceId FROM group_geofence;
+ </entry>
+
+ <entry key='database.linkGroupGeofence'>
+ INSERT INTO group_geofence (groupId, geofenceId) VALUES (:groupId, :geofenceId);
+ </entry>
+
+ <entry key='database.unlinkGroupGeofence'>
+ DELETE FROM group_geofence WHERE groupId = :groupId AND geofenceId = :geofenceId;
+ </entry>
+
+ <entry key='database.selectDeviceGeofences'>
+ SELECT deviceId, geofenceId FROM device_geofence;
+ </entry>
+
+ <entry key='database.linkDeviceGeofence'>
+ INSERT INTO device_geofence (deviceId, geofenceId) VALUES (:deviceId, :geofenceId);
+ </entry>
+
+ <entry key='database.unlinkDeviceGeofence'>
+ DELETE FROM device_geofence WHERE deviceId = :deviceId AND geofenceId = :geofenceId;
+ </entry>
+
<!-- PROTOCOL CONFIG -->
<entry key='gps103.port'>5001</entry>
diff --git a/src/org/traccar/BaseEventHandler.java b/src/org/traccar/BaseEventHandler.java
index 3e3317f0a..1ae9d2c6d 100644
--- a/src/org/traccar/BaseEventHandler.java
+++ b/src/org/traccar/BaseEventHandler.java
@@ -1,35 +1,39 @@
+/*
+ * Copyright 2016 Anton Tananaev (anton.tananaev@gmail.com)
+ *
+ * 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.traccar;
-import org.traccar.model.Device;
+import java.util.Collection;
+
import org.traccar.model.Event;
import org.traccar.model.Position;
public abstract class BaseEventHandler extends BaseDataHandler {
- private boolean isLastPosition = false;
-
- public boolean isLastPosition() {
- return isLastPosition;
- }
-
@Override
protected Position handlePosition(Position position) {
- Device device = Context.getDataManager().getDeviceById(position.getDeviceId());
- if (device != null) {
- long lastPositionId = device.getPositionId();
- if (position.getId() == lastPositionId) {
- isLastPosition = true;
+ Collection<Event> events = analyzePosition(position);
+ if (events != null) {
+ for (Event event : events) {
+ Context.getNotificationManager().updateEvent(event, position);
}
}
-
- Event event = analizePosition(position);
- if (event != null) {
- Context.getConnectionManager().updateEvent(event, position);
- }
return position;
}
- protected abstract Event analizePosition(Position position);
+ protected abstract Collection<Event> analyzePosition(Position position);
}
diff --git a/src/org/traccar/BasePipelineFactory.java b/src/org/traccar/BasePipelineFactory.java
index 634c6d6a4..b61d95171 100644
--- a/src/org/traccar/BasePipelineFactory.java
+++ b/src/org/traccar/BasePipelineFactory.java
@@ -30,6 +30,7 @@ import org.jboss.netty.channel.SimpleChannelHandler;
import org.jboss.netty.handler.logging.LoggingHandler;
import org.jboss.netty.handler.timeout.IdleStateHandler;
import org.traccar.events.CommandResultEventHandler;
+import org.traccar.events.GeofenceEventHandler;
import org.traccar.events.MotionEventHandler;
import org.traccar.events.OverspeedEventHandler;
import org.traccar.helper.Log;
@@ -50,6 +51,7 @@ public abstract class BasePipelineFactory implements ChannelPipelineFactory {
private CommandResultEventHandler commandResultEventHandler;
private OverspeedEventHandler overspeedEventHandler;
private MotionEventHandler motionEventHandler;
+ private GeofenceEventHandler geofenceEventHandler;
private static final class OpenChannelHandler extends SimpleChannelHandler {
@@ -140,6 +142,10 @@ public abstract class BasePipelineFactory implements ChannelPipelineFactory {
motionEventHandler = new MotionEventHandler();
}
+ if (Context.getConfig().getBoolean("event.geofenceHandler")) {
+ geofenceEventHandler = new GeofenceEventHandler();
+ }
+
}
protected abstract void addSpecificHandlers(ChannelPipeline pipeline);
@@ -197,6 +203,10 @@ public abstract class BasePipelineFactory implements ChannelPipelineFactory {
pipeline.addLast("MotionEventHandler", motionEventHandler);
}
+ if (geofenceEventHandler != null) {
+ pipeline.addLast("GeofenceEventHandler", geofenceEventHandler);
+ }
+
pipeline.addLast("mainHandler", new MainEventHandler());
return pipeline;
}
diff --git a/src/org/traccar/Context.java b/src/org/traccar/Context.java
index 1aba24b8c..f40db72e5 100644
--- a/src/org/traccar/Context.java
+++ b/src/org/traccar/Context.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2015 Anton Tananaev (anton.tananaev@gmail.com)
+ * Copyright 2015 - 2016 Anton Tananaev (anton.tananaev@gmail.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,7 +19,9 @@ import com.ning.http.client.AsyncHttpClient;
import org.traccar.database.ConnectionManager;
import org.traccar.database.DataManager;
import org.traccar.database.IdentityManager;
+import org.traccar.database.NotificationManager;
import org.traccar.database.PermissionsManager;
+import org.traccar.database.GeofenceManager;
import org.traccar.geocode.BingMapsReverseGeocoder;
import org.traccar.geocode.FactualReverseGeocoder;
import org.traccar.geocode.GisgraphyReverseGeocoder;
@@ -99,6 +101,18 @@ public final class Context {
return serverManager;
}
+ private static GeofenceManager geofenceManager;
+
+ public static GeofenceManager getGeofenceManager() {
+ return geofenceManager;
+ }
+
+ private static NotificationManager notificationManager;
+
+ public static NotificationManager getNotificationManager() {
+ return notificationManager;
+ }
+
private static final AsyncHttpClient ASYNC_HTTP_CLIENT = new AsyncHttpClient();
public static AsyncHttpClient getAsyncHttpClient() {
@@ -179,7 +193,12 @@ public final class Context {
connectionManager = new ConnectionManager(dataManager);
+ geofenceManager = new GeofenceManager(dataManager);
+
+ notificationManager = new NotificationManager(dataManager);
+
serverManager = new ServerManager();
+
}
public static void init(IdentityManager testIdentityManager) {
diff --git a/src/org/traccar/api/resource/DeviceGeofenceResource.java b/src/org/traccar/api/resource/DeviceGeofenceResource.java
new file mode 100644
index 000000000..27535617d
--- /dev/null
+++ b/src/org/traccar/api/resource/DeviceGeofenceResource.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2016 Anton Tananaev (anton.tananaev@gmail.com)
+ *
+ * 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.traccar.api.resource;
+
+import org.traccar.Context;
+import org.traccar.api.BaseResource;
+import org.traccar.model.DeviceGeofence;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import java.sql.SQLException;
+
+@Path("devices/geofences")
+@Produces(MediaType.APPLICATION_JSON)
+@Consumes(MediaType.APPLICATION_JSON)
+public class DeviceGeofenceResource extends BaseResource {
+
+ @POST
+ public Response add(DeviceGeofence entity) throws SQLException {
+ Context.getPermissionsManager().checkReadonly(getUserId());
+ Context.getPermissionsManager().checkDevice(getUserId(), entity.getDeviceId());
+ Context.getPermissionsManager().checkGeofence(getUserId(), entity.getGeofenceId());
+ Context.getDataManager().linkDeviceGeofence(entity.getDeviceId(), entity.getGeofenceId());
+ Context.getGeofenceManager().refresh();
+ return Response.ok(entity).build();
+ }
+
+ @DELETE
+ public Response remove(DeviceGeofence entity) throws SQLException {
+ Context.getPermissionsManager().checkReadonly(getUserId());
+ Context.getPermissionsManager().checkDevice(getUserId(), entity.getDeviceId());
+ Context.getPermissionsManager().checkGeofence(getUserId(), entity.getGeofenceId());
+ Context.getDataManager().unlinkDeviceGeofence(entity.getDeviceId(), entity.getGeofenceId());
+ Context.getGeofenceManager().refresh();
+ return Response.noContent().build();
+ }
+
+}
diff --git a/src/org/traccar/api/resource/DevicePermissionResource.java b/src/org/traccar/api/resource/DevicePermissionResource.java
index f77375cba..2ef436f11 100644
--- a/src/org/traccar/api/resource/DevicePermissionResource.java
+++ b/src/org/traccar/api/resource/DevicePermissionResource.java
@@ -38,6 +38,7 @@ public class DevicePermissionResource extends BaseResource {
Context.getPermissionsManager().checkAdmin(getUserId());
Context.getDataManager().linkDevice(entity.getUserId(), entity.getDeviceId());
Context.getPermissionsManager().refresh();
+ Context.getGeofenceManager().refresh();
return Response.ok(entity).build();
}
@@ -46,6 +47,7 @@ public class DevicePermissionResource extends BaseResource {
Context.getPermissionsManager().checkAdmin(getUserId());
Context.getDataManager().unlinkDevice(entity.getUserId(), entity.getDeviceId());
Context.getPermissionsManager().refresh();
+ Context.getGeofenceManager().refresh();
return Response.noContent().build();
}
diff --git a/src/org/traccar/api/resource/DeviceResource.java b/src/org/traccar/api/resource/DeviceResource.java
index 6c0ef32ca..d6032bb1e 100644
--- a/src/org/traccar/api/resource/DeviceResource.java
+++ b/src/org/traccar/api/resource/DeviceResource.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2015 Anton Tananaev (anton.tananaev@gmail.com)
+ * Copyright 2015 - 2016 Anton Tananaev (anton.tananaev@gmail.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -44,7 +44,7 @@ public class DeviceResource extends BaseResource {
@QueryParam("all") boolean all, @QueryParam("userId") long userId) throws SQLException {
if (all) {
Context.getPermissionsManager().checkAdmin(getUserId());
- return Context.getDataManager().getAllDevices();
+ return Context.getDataManager().getAllDevicesCached();
} else {
if (userId == 0) {
userId = getUserId();
@@ -60,6 +60,7 @@ public class DeviceResource extends BaseResource {
Context.getDataManager().addDevice(entity);
Context.getDataManager().linkDevice(getUserId(), entity.getId());
Context.getPermissionsManager().refresh();
+ Context.getGeofenceManager().refresh();
return Response.ok(entity).build();
}
@@ -79,6 +80,7 @@ public class DeviceResource extends BaseResource {
Context.getPermissionsManager().checkDevice(getUserId(), id);
Context.getDataManager().removeDevice(id);
Context.getPermissionsManager().refresh();
+ Context.getGeofenceManager().refresh();
return Response.noContent().build();
}
diff --git a/src/org/traccar/api/resource/GeofencePermissionResource.java b/src/org/traccar/api/resource/GeofencePermissionResource.java
new file mode 100644
index 000000000..329c72b07
--- /dev/null
+++ b/src/org/traccar/api/resource/GeofencePermissionResource.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2016 Anton Tananaev (anton.tananaev@gmail.com)
+ *
+ * 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.traccar.api.resource;
+
+import org.traccar.Context;
+import org.traccar.api.BaseResource;
+import org.traccar.model.GeofencePermission;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import java.sql.SQLException;
+
+@Path("permissions/geofences")
+@Produces(MediaType.APPLICATION_JSON)
+@Consumes(MediaType.APPLICATION_JSON)
+public class GeofencePermissionResource extends BaseResource {
+
+ @POST
+ public Response add(GeofencePermission entity) throws SQLException {
+ Context.getPermissionsManager().checkReadonly(getUserId());
+ Context.getPermissionsManager().checkUser(getUserId(), entity.getUserId());
+ Context.getPermissionsManager().checkGeofence(getUserId(), entity.getGeofenceId());
+ Context.getDataManager().linkGeofence(entity.getUserId(), entity.getGeofenceId());
+ Context.getGeofenceManager().refresh();
+ return Response.ok(entity).build();
+ }
+
+ @DELETE
+ public Response remove(GeofencePermission entity) throws SQLException {
+ Context.getPermissionsManager().checkReadonly(getUserId());
+ Context.getPermissionsManager().checkUser(getUserId(), entity.getUserId());
+ Context.getPermissionsManager().checkGeofence(getUserId(), entity.getGeofenceId());
+ Context.getDataManager().unlinkGeofence(entity.getUserId(), entity.getGeofenceId());
+ Context.getGeofenceManager().refresh();
+ return Response.noContent().build();
+ }
+
+}
diff --git a/src/org/traccar/api/resource/GeofenceResource.java b/src/org/traccar/api/resource/GeofenceResource.java
new file mode 100644
index 000000000..9c80e61a1
--- /dev/null
+++ b/src/org/traccar/api/resource/GeofenceResource.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2016 Anton Tananaev (anton.tananaev@gmail.com)
+ *
+ * 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.traccar.api.resource;
+
+import org.traccar.Context;
+import org.traccar.api.BaseResource;
+import org.traccar.model.Geofence;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import java.sql.SQLException;
+import java.util.Collection;
+
+@Path("geofences")
+@Produces(MediaType.APPLICATION_JSON)
+@Consumes(MediaType.APPLICATION_JSON)
+public class GeofenceResource extends BaseResource {
+
+ @GET
+ public Collection<Geofence> get(
+ @QueryParam("all") boolean all, @QueryParam("userId") long userId) throws SQLException {
+ if (all) {
+ Context.getPermissionsManager().checkAdmin(getUserId());
+ return Context.getGeofenceManager().getAllGeofences();
+ } else {
+ if (userId == 0) {
+ userId = getUserId();
+ }
+ Context.getPermissionsManager().checkUser(getUserId(), userId);
+ return Context.getGeofenceManager().getUserGeofences(userId);
+ }
+ }
+
+ @POST
+ public Response add(Geofence entity) throws SQLException {
+ Context.getPermissionsManager().checkReadonly(getUserId());
+ Context.getDataManager().addGeofence(entity);
+ Context.getDataManager().linkGeofence(getUserId(), entity.getId());
+ Context.getGeofenceManager().refresh();
+ return Response.ok(entity).build();
+ }
+
+ @Path("{id}")
+ @PUT
+ public Response update(@PathParam("id") long id, Geofence entity) throws SQLException {
+ Context.getPermissionsManager().checkReadonly(getUserId());
+ Context.getPermissionsManager().checkGeofence(getUserId(), id);
+ Context.getGeofenceManager().updateGeofence(entity);
+ return Response.ok(entity).build();
+ }
+
+ @Path("{id}")
+ @DELETE
+ public Response remove(@PathParam("id") long id) throws SQLException {
+ Context.getPermissionsManager().checkReadonly(getUserId());
+ Context.getPermissionsManager().checkGeofence(getUserId(), id);
+ Context.getDataManager().removeGeofence(id);
+ Context.getGeofenceManager().refresh();
+ return Response.noContent().build();
+ }
+
+}
diff --git a/src/org/traccar/api/resource/GroupGeofenceResource.java b/src/org/traccar/api/resource/GroupGeofenceResource.java
new file mode 100644
index 000000000..1ef495a86
--- /dev/null
+++ b/src/org/traccar/api/resource/GroupGeofenceResource.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2016 Anton Tananaev (anton.tananaev@gmail.com)
+ *
+ * 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.traccar.api.resource;
+
+import org.traccar.Context;
+import org.traccar.api.BaseResource;
+import org.traccar.model.GroupGeofence;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import java.sql.SQLException;
+
+@Path("groups/geofences")
+@Produces(MediaType.APPLICATION_JSON)
+@Consumes(MediaType.APPLICATION_JSON)
+public class GroupGeofenceResource extends BaseResource {
+
+ @POST
+ public Response add(GroupGeofence entity) throws SQLException {
+ Context.getPermissionsManager().checkReadonly(getUserId());
+ Context.getPermissionsManager().checkGroup(getUserId(), entity.getGroupId());
+ Context.getPermissionsManager().checkGeofence(getUserId(), entity.getGeofenceId());
+ Context.getDataManager().linkGroupGeofence(entity.getGroupId(), entity.getGeofenceId());
+ Context.getGeofenceManager().refresh();
+ return Response.ok(entity).build();
+ }
+
+ @DELETE
+ public Response remove(GroupGeofence entity) throws SQLException {
+ Context.getPermissionsManager().checkReadonly(getUserId());
+ Context.getPermissionsManager().checkGroup(getUserId(), entity.getGroupId());
+ Context.getPermissionsManager().checkGeofence(getUserId(), entity.getGeofenceId());
+ Context.getDataManager().unlinkGroupGeofence(entity.getGroupId(), entity.getGeofenceId());
+ Context.getGeofenceManager().refresh();
+ return Response.noContent().build();
+ }
+
+}
diff --git a/src/org/traccar/api/resource/GroupPermissionResource.java b/src/org/traccar/api/resource/GroupPermissionResource.java
index b8ec4ae3c..564a379d2 100644
--- a/src/org/traccar/api/resource/GroupPermissionResource.java
+++ b/src/org/traccar/api/resource/GroupPermissionResource.java
@@ -38,6 +38,7 @@ public class GroupPermissionResource extends BaseResource {
Context.getPermissionsManager().checkAdmin(getUserId());
Context.getDataManager().linkGroup(entity.getUserId(), entity.getGroupId());
Context.getPermissionsManager().refresh();
+ Context.getGeofenceManager().refresh();
return Response.ok(entity).build();
}
@@ -46,6 +47,7 @@ public class GroupPermissionResource extends BaseResource {
Context.getPermissionsManager().checkAdmin(getUserId());
Context.getDataManager().unlinkGroup(entity.getUserId(), entity.getGroupId());
Context.getPermissionsManager().refresh();
+ Context.getGeofenceManager().refresh();
return Response.noContent().build();
}
diff --git a/src/org/traccar/api/resource/GroupResource.java b/src/org/traccar/api/resource/GroupResource.java
index e22796645..dda9ab03b 100644
--- a/src/org/traccar/api/resource/GroupResource.java
+++ b/src/org/traccar/api/resource/GroupResource.java
@@ -59,6 +59,7 @@ public class GroupResource extends BaseResource {
Context.getDataManager().addGroup(entity);
Context.getDataManager().linkGroup(getUserId(), entity.getId());
Context.getPermissionsManager().refresh();
+ Context.getGeofenceManager().refresh();
return Response.ok(entity).build();
}
@@ -78,6 +79,7 @@ public class GroupResource extends BaseResource {
Context.getPermissionsManager().checkGroup(getUserId(), id);
Context.getDataManager().removeGroup(id);
Context.getPermissionsManager().refresh();
+ Context.getGeofenceManager().refresh();
return Response.noContent().build();
}
diff --git a/src/org/traccar/api/resource/UserResource.java b/src/org/traccar/api/resource/UserResource.java
index 0b307ab88..7cc4ed09a 100644
--- a/src/org/traccar/api/resource/UserResource.java
+++ b/src/org/traccar/api/resource/UserResource.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2015 Anton Tananaev (anton.tananaev@gmail.com)
+ * Copyright 2015 - 2016 Anton Tananaev (anton.tananaev@gmail.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -52,6 +52,7 @@ public class UserResource extends BaseResource {
}
Context.getDataManager().addUser(entity);
Context.getPermissionsManager().refresh();
+ Context.getGeofenceManager().refresh();
return Response.ok(entity).build();
}
@@ -65,6 +66,7 @@ public class UserResource extends BaseResource {
}
Context.getDataManager().updateUser(entity);
Context.getPermissionsManager().refresh();
+ Context.getGeofenceManager().refresh();
return Response.ok(entity).build();
}
@@ -74,6 +76,7 @@ public class UserResource extends BaseResource {
Context.getPermissionsManager().checkUser(getUserId(), id);
Context.getDataManager().removeUser(id);
Context.getPermissionsManager().refresh();
+ Context.getGeofenceManager().refresh();
return Response.noContent().build();
}
diff --git a/src/org/traccar/database/ConnectionManager.java b/src/org/traccar/database/ConnectionManager.java
index ec5903548..8a1debdfa 100644
--- a/src/org/traccar/database/ConnectionManager.java
+++ b/src/org/traccar/database/ConnectionManager.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2015 Anton Tananaev (anton.tananaev@gmail.com)
+ * Copyright 2015 - 2016 Anton Tananaev (anton.tananaev@gmail.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -94,7 +94,7 @@ public class ConnectionManager {
if (status.equals(Device.STATUS_ONLINE)) {
event.setType(Event.TYPE_DEVICE_ONLINE);
}
- updateEvent(event, null);
+ Context.getNotificationManager().updateEvent(event, null);
}
device.setStatus(status);
@@ -147,18 +147,10 @@ public class ConnectionManager {
}
}
- public synchronized void updateEvent(Event event, Position position) {
- long deviceId = event.getDeviceId();
- try {
- Context.getDataManager().addEvent(event);
- } catch (SQLException error) {
- Log.warning(error);
- }
- for (long userId : Context.getPermissionsManager().getDeviceUsers(deviceId)) {
- if (listeners.containsKey(userId)) {
- for (UpdateListener listener : listeners.get(userId)) {
- listener.onUpdateEvent(event, position);
- }
+ public synchronized void updateEvent(long userId, Event event, Position position) {
+ if (listeners.containsKey(userId)) {
+ for (UpdateListener listener : listeners.get(userId)) {
+ listener.onUpdateEvent(event, position);
}
}
}
diff --git a/src/org/traccar/database/DataManager.java b/src/org/traccar/database/DataManager.java
index 67b7d1e55..860bf1b84 100644
--- a/src/org/traccar/database/DataManager.java
+++ b/src/org/traccar/database/DataManager.java
@@ -48,11 +48,15 @@ import org.traccar.helper.Log;
import org.traccar.model.Device;
import org.traccar.model.DevicePermission;
import org.traccar.model.Event;
+import org.traccar.model.Geofence;
import org.traccar.model.Group;
+import org.traccar.model.GroupGeofence;
import org.traccar.model.GroupPermission;
import org.traccar.model.Position;
import org.traccar.model.Server;
import org.traccar.model.User;
+import org.traccar.model.DeviceGeofence;
+import org.traccar.model.GeofencePermission;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
@@ -146,9 +150,17 @@ public class DataManager implements IdentityManager {
if (force || System.currentTimeMillis() - devicesLastUpdate > dataRefreshDelay) {
devicesById.clear();
devicesByUniqueId.clear();
+ ConnectionManager connectionManager = Context.getConnectionManager();
+ GeofenceManager geofenceManager = Context.getGeofenceManager();
for (Device device : getAllDevices()) {
devicesById.put(device.getId(), device);
devicesByUniqueId.put(device.getUniqueId(), device);
+ if (connectionManager != null && geofenceManager != null) {
+ Position lastPosition = connectionManager.getLastPosition(device.getId());
+ if (lastPosition != null) {
+ device.setGeofenceIds(geofenceManager.getCurrentDeviceGeofences(lastPosition));
+ }
+ }
}
devicesLastUpdate = System.currentTimeMillis();
}
@@ -334,11 +346,34 @@ public class DataManager implements IdentityManager {
.executeQuery(GroupPermission.class);
}
- public Collection<Device> getAllDevices() throws SQLException {
+ private Collection<Device> getAllDevices() throws SQLException {
return QueryBuilder.create(dataSource, getQuery("database.selectDevicesAll"))
.executeQuery(Device.class);
}
+ public Collection<Device> getAllDevicesCached() {
+ boolean forceUpdate;
+ devicesLock.readLock().lock();
+ try {
+ forceUpdate = devicesById.values().isEmpty();
+ } finally {
+ devicesLock.readLock().unlock();
+ }
+
+ try {
+ updateDeviceCache(forceUpdate);
+ } catch (SQLException e) {
+ Log.warning(e);
+ }
+
+ devicesLock.readLock().lock();
+ try {
+ return devicesById.values();
+ } finally {
+ devicesLock.readLock().unlock();
+ }
+ }
+
public Collection<Device> getDevices(long userId) throws SQLException {
Collection<Device> devices = new ArrayList<>();
for (long id : Context.getPermissionsManager().getDevicePermissions(userId)) {
@@ -520,8 +555,93 @@ public class DataManager implements IdentityManager {
public Collection<Event> getLastEvents(long deviceId, String type, int interval) throws SQLException {
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.SECOND, -interval);
- Date to = calendar.getTime();
- return getEvents(deviceId, type, new Date(), to);
+ Date from = calendar.getTime();
+ return getEvents(deviceId, type, from, new Date());
+ }
+
+ public Collection<Geofence> getGeofences() throws SQLException {
+ return QueryBuilder.create(dataSource, getQuery("database.selectGeofencesAll"))
+ .executeQuery(Geofence.class);
+ }
+
+ public Geofence getGeofence(long geofenceId) throws SQLException {
+ return QueryBuilder.create(dataSource, getQuery("database.selectGeofences"))
+ .setLong("id", geofenceId)
+ .executeQuerySingle(Geofence.class);
+ }
+
+ public void addGeofence(Geofence geofence) throws SQLException {
+ geofence.setId(QueryBuilder.create(dataSource, getQuery("database.insertGeofence"), true)
+ .setObject(geofence)
+ .executeUpdate());
+ }
+
+ public void updateGeofence(Geofence geofence) throws SQLException {
+ QueryBuilder.create(dataSource, getQuery("database.updateGeofence"))
+ .setObject(geofence)
+ .executeUpdate();
+ }
+
+ public void removeGeofence(long geofenceId) throws SQLException {
+ QueryBuilder.create(dataSource, getQuery("database.deleteGeofence"))
+ .setLong("id", geofenceId)
+ .executeUpdate();
+ }
+
+ public Collection<GeofencePermission> getGeofencePermissions() throws SQLException {
+ return QueryBuilder.create(dataSource, getQuery("database.selectGeofencePermissions"))
+ .executeQuery(GeofencePermission.class);
+ }
+
+ public void linkGeofence(long userId, long geofenceId) throws SQLException {
+ QueryBuilder.create(dataSource, getQuery("database.linkGeofence"))
+ .setLong("userId", userId)
+ .setLong("geofenceId", geofenceId)
+ .executeUpdate();
}
+ public void unlinkGeofence(long userId, long geofenceId) throws SQLException {
+ QueryBuilder.create(dataSource, getQuery("database.unlinkGeofence"))
+ .setLong("userId", userId)
+ .setLong("geofenceId", geofenceId)
+ .executeUpdate();
+ }
+
+ public Collection<GroupGeofence> getGroupGeofences() throws SQLException {
+ return QueryBuilder.create(dataSource, getQuery("database.selectGroupGeofences"))
+ .executeQuery(GroupGeofence.class);
+ }
+
+ public void linkGroupGeofence(long groupId, long geofenceId) throws SQLException {
+ QueryBuilder.create(dataSource, getQuery("database.linkGroupGeofence"))
+ .setLong("groupId", groupId)
+ .setLong("geofenceId", geofenceId)
+ .executeUpdate();
+ }
+
+ public void unlinkGroupGeofence(long groupId, long geofenceId) throws SQLException {
+ QueryBuilder.create(dataSource, getQuery("database.unlinkGroupGeofence"))
+ .setLong("groupId", groupId)
+ .setLong("geofenceId", geofenceId)
+ .executeUpdate();
+ }
+
+ public Collection<DeviceGeofence> getDeviceGeofences() throws SQLException {
+ return QueryBuilder.create(dataSource, getQuery("database.selectDeviceGeofences"))
+ .executeQuery(DeviceGeofence.class);
+ }
+
+ public void linkDeviceGeofence(long deviceId, long geofenceId) throws SQLException {
+ QueryBuilder.create(dataSource, getQuery("database.linkDeviceGeofence"))
+ .setLong("deviceId", deviceId)
+ .setLong("geofenceId", geofenceId)
+ .executeUpdate();
+ }
+
+ public void unlinkDeviceGeofence(long deviceId, long geofenceId) throws SQLException {
+ QueryBuilder.create(dataSource, getQuery("database.unlinkDeviceGeofence"))
+ .setLong("deviceId", deviceId)
+ .setLong("geofenceId", geofenceId)
+ .executeUpdate();
+ }
}
diff --git a/src/org/traccar/database/GeofenceManager.java b/src/org/traccar/database/GeofenceManager.java
new file mode 100644
index 000000000..0a07e2cf3
--- /dev/null
+++ b/src/org/traccar/database/GeofenceManager.java
@@ -0,0 +1,227 @@
+/*
+ * Copyright 2016 Anton Tananaev (anton.tananaev@gmail.com)
+ *
+ * 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.traccar.database;
+
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+import org.traccar.Context;
+import org.traccar.helper.Log;
+import org.traccar.model.Device;
+import org.traccar.model.Geofence;
+import org.traccar.model.GroupGeofence;
+import org.traccar.model.Position;
+import org.traccar.model.DeviceGeofence;
+import org.traccar.model.GeofencePermission;
+
+public class GeofenceManager {
+
+ private final DataManager dataManager;
+
+ private final Map<Long, Geofence> geofences = new HashMap<>();
+ private final Map<Long, Set<Long>> userGeofences = new HashMap<>();
+ private final Map<Long, Set<Long>> groupGeofences = new HashMap<>();
+
+ private final Map<Long, Set<Long>> deviceGeofencesWithGroups = new HashMap<>();
+ private final Map<Long, Set<Long>> deviceGeofences = new HashMap<>();
+
+ private final ReadWriteLock deviceGeofencesLock = new ReentrantReadWriteLock();
+ private final ReadWriteLock geofencesLock = new ReentrantReadWriteLock();
+ private final ReadWriteLock groupGeofencesLock = new ReentrantReadWriteLock();
+
+ public GeofenceManager(DataManager dataManager) {
+ this.dataManager = dataManager;
+ refresh();
+ }
+
+ public Set<Long> getUserGeofencesIds(long userId) {
+ if (!userGeofences.containsKey(userId)) {
+ userGeofences.put(userId, new HashSet<Long>());
+ }
+ return userGeofences.get(userId);
+ }
+
+ private Set<Long> getGroupGeofences(long groupId) {
+ if (!groupGeofences.containsKey(groupId)) {
+ groupGeofences.put(groupId, new HashSet<Long>());
+ }
+ return groupGeofences.get(groupId);
+ }
+
+ public Set<Long> getAllDeviceGeofences(long deviceId) {
+ deviceGeofencesLock.readLock().lock();
+ try {
+ return getDeviceGeofences(deviceGeofencesWithGroups, deviceId);
+ } finally {
+ deviceGeofencesLock.readLock().unlock();
+ }
+
+ }
+
+ public Set<Long> getDeviceGeofences(long deviceId) {
+ deviceGeofencesLock.readLock().lock();
+ try {
+ return getDeviceGeofences(deviceGeofences, deviceId);
+ } finally {
+ deviceGeofencesLock.readLock().unlock();
+ }
+ }
+
+ private Set<Long> getDeviceGeofences(Map<Long, Set<Long>> deviceGeofences, long deviceId) {
+ if (!deviceGeofences.containsKey(deviceId)) {
+ deviceGeofences.put(deviceId, new HashSet<Long>());
+ }
+ return deviceGeofences.get(deviceId);
+ }
+
+ public final void refresh() {
+ if (dataManager != null) {
+ try {
+ geofencesLock.writeLock().lock();
+ groupGeofencesLock.writeLock().lock();
+ deviceGeofencesLock.writeLock().lock();
+ try {
+ geofences.clear();
+ for (Geofence geofence : dataManager.getGeofences()) {
+ geofences.put(geofence.getId(), geofence);
+ }
+
+ userGeofences.clear();
+ for (GeofencePermission geofencePermission : dataManager.getGeofencePermissions()) {
+ getUserGeofencesIds(geofencePermission.getUserId()).add(geofencePermission.getGeofenceId());
+ }
+
+ groupGeofences.clear();
+ for (GroupGeofence groupGeofence : dataManager.getGroupGeofences()) {
+ getGroupGeofences(groupGeofence.getGroupId()).add(groupGeofence.getGeofenceId());
+ }
+
+ deviceGeofences.clear();
+ deviceGeofencesWithGroups.clear();
+
+ for (DeviceGeofence deviceGeofence : dataManager.getDeviceGeofences()) {
+ getDeviceGeofences(deviceGeofences, deviceGeofence.getDeviceId())
+ .add(deviceGeofence.getGeofenceId());
+ getDeviceGeofences(deviceGeofencesWithGroups, deviceGeofence.getDeviceId())
+ .add(deviceGeofence.getGeofenceId());
+ }
+
+ for (Device device : dataManager.getAllDevicesCached()) {
+ long groupId = device.getGroupId();
+ while (groupId != 0) {
+ getDeviceGeofences(deviceGeofencesWithGroups,
+ device.getId()).addAll(getGroupGeofences(groupId));
+ groupId = dataManager.getGroupById(groupId).getGroupId();
+ }
+ List<Long> deviceGeofenceIds = device.getGeofenceIds();
+ if (deviceGeofenceIds == null) {
+ deviceGeofenceIds = new ArrayList<Long>();
+ } else {
+ deviceGeofenceIds.clear();
+ }
+ Position lastPosition = Context.getConnectionManager().getLastPosition(device.getId());
+ if (lastPosition != null) {
+ for (Long geofenceId : deviceGeofencesWithGroups.get(device.getId())) {
+ if (getGeofence(geofenceId).getGeometry()
+ .containsPoint(lastPosition.getLatitude(), lastPosition.getLongitude())) {
+ deviceGeofenceIds.add(geofenceId);
+ }
+ }
+ }
+ device.setGeofenceIds(deviceGeofenceIds);
+ }
+
+ } finally {
+ geofencesLock.writeLock().unlock();
+ groupGeofencesLock.writeLock().unlock();
+ deviceGeofencesLock.writeLock().unlock();
+ }
+
+ } catch (SQLException error) {
+ Log.warning(error);
+ }
+ }
+ }
+
+ public final Collection<Geofence> getAllGeofences() {
+ geofencesLock.readLock().lock();
+ try {
+ return geofences.values();
+ } finally {
+ geofencesLock.readLock().unlock();
+ }
+ }
+
+ public final Collection<Geofence> getUserGeofences(long userId) {
+ geofencesLock.readLock().lock();
+ try {
+ Collection<Geofence> result = new LinkedList<>();
+ for (Long geofenceId : getUserGeofencesIds(userId)) {
+ result.add(getGeofence(geofenceId));
+ }
+ return result;
+ } finally {
+ geofencesLock.readLock().unlock();
+ }
+ }
+
+ public final Geofence getGeofence(Long geofenceId) {
+ geofencesLock.readLock().lock();
+ try {
+ return geofences.get(geofenceId);
+ } finally {
+ geofencesLock.readLock().unlock();
+ }
+ }
+
+ public final void updateGeofence(Geofence geofence) {
+ geofencesLock.writeLock().lock();
+ try {
+ geofences.put(geofence.getId(), geofence);
+ } finally {
+ geofencesLock.writeLock().unlock();
+ }
+ try {
+ dataManager.updateGeofence(geofence);
+ } catch (SQLException error) {
+ Log.warning(error);
+ }
+ }
+
+ public boolean checkGeofence(long userId, long geofenceId) {
+ return getUserGeofencesIds(userId).contains(geofenceId);
+ }
+
+ public List<Long> getCurrentDeviceGeofences(Position position) {
+ List<Long> result = new ArrayList<Long>();
+ for (Long geofenceId : getAllDeviceGeofences(position.getDeviceId())) {
+ if (getGeofence(geofenceId).getGeometry().containsPoint(position.getLatitude(), position.getLongitude())) {
+ result.add(geofenceId);
+ }
+ }
+ return result;
+ }
+
+}
diff --git a/src/org/traccar/database/NotificationManager.java b/src/org/traccar/database/NotificationManager.java
new file mode 100644
index 000000000..7593367a3
--- /dev/null
+++ b/src/org/traccar/database/NotificationManager.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2016 Anton Tananaev (anton.tananaev@gmail.com)
+ *
+ * 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.traccar.database;
+
+import java.sql.SQLException;
+import java.util.Collection;
+import java.util.Set;
+
+import org.traccar.Context;
+import org.traccar.helper.Log;
+import org.traccar.model.Event;
+import org.traccar.model.Position;
+
+public class NotificationManager {
+
+ private final DataManager dataManager;
+
+ public NotificationManager(DataManager dataManager) {
+ this.dataManager = dataManager;
+ }
+
+ public void updateEvent(Event event, Position position) {
+ try {
+ dataManager.addEvent(event);
+ } catch (SQLException error) {
+ Log.warning(error);
+ }
+
+ Set<Long> users = Context.getPermissionsManager().getDeviceUsers(event.getDeviceId());
+ for (Long userId : users) {
+ if (event.getGeofenceId() == 0
+ || Context.getGeofenceManager().checkGeofence(userId, event.getGeofenceId())) {
+ Context.getConnectionManager().updateEvent(userId, event, position);
+ }
+ }
+ }
+
+ public void updateEvents(Collection<Event> events, Position position) {
+
+ for (Event event : events) {
+ updateEvent(event, position);
+ }
+ }
+}
diff --git a/src/org/traccar/database/PermissionsManager.java b/src/org/traccar/database/PermissionsManager.java
index 08d44b382..b6dd2e2a9 100644
--- a/src/org/traccar/database/PermissionsManager.java
+++ b/src/org/traccar/database/PermissionsManager.java
@@ -15,6 +15,7 @@
*/
package org.traccar.database;
+import org.traccar.Context;
import org.traccar.helper.Log;
import org.traccar.model.Device;
import org.traccar.model.DevicePermission;
@@ -77,7 +78,7 @@ public class PermissionsManager {
users.put(user.getId(), user);
}
- GroupTree groupTree = new GroupTree(dataManager.getAllGroups(), dataManager.getAllDevices());
+ GroupTree groupTree = new GroupTree(dataManager.getAllGroups(), dataManager.getAllDevicesCached());
for (GroupPermission permission : dataManager.getGroupPermissions()) {
Set<Long> userGroupPermissions = getGroupPermissions(permission.getUserId());
Set<Long> userDevicePermissions = getDevicePermissions(permission.getUserId());
@@ -145,4 +146,10 @@ public class PermissionsManager {
}
}
+ public void checkGeofence(long userId, long geofenceId) throws SecurityException {
+ if (!Context.getGeofenceManager().checkGeofence(userId, geofenceId) && !isAdmin(userId)) {
+ throw new SecurityException("Geofence access denied");
+ }
+ }
+
}
diff --git a/src/org/traccar/events/CommandResultEventHandler.java b/src/org/traccar/events/CommandResultEventHandler.java
index 23c62566a..3f4ff521b 100644
--- a/src/org/traccar/events/CommandResultEventHandler.java
+++ b/src/org/traccar/events/CommandResultEventHandler.java
@@ -1,5 +1,23 @@
+/*
+ * Copyright 2016 Anton Tananaev (anton.tananaev@gmail.com)
+ *
+ * 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.traccar.events;
+import java.util.ArrayList;
+import java.util.Collection;
+
import org.traccar.BaseEventHandler;
import org.traccar.model.Event;
import org.traccar.model.Position;
@@ -7,10 +25,12 @@ import org.traccar.model.Position;
public class CommandResultEventHandler extends BaseEventHandler {
@Override
- protected Event analizePosition(Position position) {
+ protected Collection<Event> analyzePosition(Position position) {
Object commandResult = position.getAttributes().get(Position.KEY_RESULT);
if (commandResult != null) {
- return new Event(Event.TYPE_COMMAND_RESULT, position.getDeviceId(), position.getId());
+ Collection<Event> events = new ArrayList<>();
+ events.add(new Event(Event.TYPE_COMMAND_RESULT, position.getDeviceId(), position.getId()));
+ return events;
}
return null;
}
diff --git a/src/org/traccar/events/GeofenceEventHandler.java b/src/org/traccar/events/GeofenceEventHandler.java
new file mode 100644
index 000000000..e9a4a640f
--- /dev/null
+++ b/src/org/traccar/events/GeofenceEventHandler.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2016 Anton Tananaev (anton.tananaev@gmail.com)
+ *
+ * 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.traccar.events;
+
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import org.traccar.BaseEventHandler;
+import org.traccar.Context;
+import org.traccar.database.DataManager;
+import org.traccar.database.GeofenceManager;
+import org.traccar.helper.Log;
+import org.traccar.model.Device;
+import org.traccar.model.Event;
+import org.traccar.model.Position;
+
+public class GeofenceEventHandler extends BaseEventHandler {
+
+ private int suppressRepeated;
+ private GeofenceManager geofenceManager;
+ private DataManager dataManager;
+
+ public GeofenceEventHandler() {
+ suppressRepeated = Context.getConfig().getInteger("event.suppressRepeated", 60);
+ geofenceManager = Context.getGeofenceManager();
+ dataManager = Context.getDataManager();
+ }
+
+ @Override
+ protected Collection<Event> analyzePosition(Position position) {
+ Device device = dataManager.getDeviceById(position.getDeviceId());
+ if (device == null) {
+ return null;
+ }
+ if (position.getId() != device.getPositionId() || !position.getValid()) {
+ return null;
+ }
+
+ List<Long> currentGeofences = geofenceManager.getCurrentDeviceGeofences(position);
+ List<Long> oldGeofences = new ArrayList<Long>();
+ if (device.getGeofenceIds() != null) {
+ oldGeofences.addAll(device.getGeofenceIds());
+ }
+ List<Long> newGeofences = new ArrayList<Long>(currentGeofences);
+ newGeofences.removeAll(oldGeofences);
+ oldGeofences.removeAll(currentGeofences);
+
+ device.setGeofenceIds(currentGeofences);
+
+ Collection<Event> events = new ArrayList<>();
+ try {
+ if (dataManager.getLastEvents(position.getDeviceId(),
+ Event.TYPE_GEOFENCE_ENTER, suppressRepeated).isEmpty()) {
+ for (Long geofenceId : newGeofences) {
+ Event event = new Event(Event.TYPE_GEOFENCE_ENTER, position.getDeviceId(), position.getId());
+ event.setGeofenceId(geofenceId);
+ events.add(event);
+ }
+ }
+ } catch (SQLException error) {
+ Log.warning(error);
+ }
+ try {
+ if (dataManager.getLastEvents(position.getDeviceId(),
+ Event.TYPE_GEOFENCE_EXIT, suppressRepeated).isEmpty()) {
+ for (Long geofenceId : oldGeofences) {
+ Event event = new Event(Event.TYPE_GEOFENCE_EXIT, position.getDeviceId(), position.getId());
+ event.setGeofenceId(geofenceId);
+ events.add(event);
+ }
+ }
+ } catch (SQLException error) {
+ Log.warning(error);
+ }
+ return events;
+ }
+}
diff --git a/src/org/traccar/events/MotionEventHandler.java b/src/org/traccar/events/MotionEventHandler.java
index a3b81ddc4..d10513d26 100644
--- a/src/org/traccar/events/MotionEventHandler.java
+++ b/src/org/traccar/events/MotionEventHandler.java
@@ -1,6 +1,23 @@
+/*
+ * Copyright 2016 Anton Tananaev (anton.tananaev@gmail.com)
+ *
+ * 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.traccar.events;
import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Collection;
import org.traccar.BaseEventHandler;
import org.traccar.Context;
@@ -19,37 +36,45 @@ public class MotionEventHandler extends BaseEventHandler {
}
@Override
- protected Event analizePosition(Position position) {
- Event event = null;
+ protected Collection<Event> analyzePosition(Position position) {
- if (!isLastPosition()) {
- return event;
+ Device device = Context.getDataManager().getDeviceById(position.getDeviceId());
+ if (device == null) {
+ return null;
+ }
+ if (position.getId() != device.getPositionId() || !position.getValid()) {
+ return null;
}
+ Collection<Event> result = null;
double speed = position.getSpeed();
boolean valid = position.getValid();
- Device device = Context.getIdentityManager().getDeviceById(position.getDeviceId());
- if (device == null) {
- return event;
- }
String motion = device.getMotion();
+ if (motion == null) {
+ motion = Device.STATUS_STOPPED;
+ }
if (valid && speed > SPEED_THRESHOLD && !motion.equals(Device.STATUS_MOVING)) {
Context.getConnectionManager().updateDevice(position.getDeviceId(), Device.STATUS_MOVING, null);
- event = new Event(Event.TYPE_DEVICE_MOVING, position.getDeviceId(), position.getId());
+ result = new ArrayList<>();
+ result.add(new Event(Event.TYPE_DEVICE_MOVING, position.getDeviceId(), position.getId()));
} else if (valid && speed < SPEED_THRESHOLD && motion.equals(Device.STATUS_MOVING)) {
Context.getConnectionManager().updateDevice(position.getDeviceId(), Device.STATUS_STOPPED, null);
- event = new Event(Event.TYPE_DEVICE_STOPPED, position.getDeviceId(), position.getId());
+ result = new ArrayList<>();
+ result.add(new Event(Event.TYPE_DEVICE_STOPPED, position.getDeviceId(), position.getId()));
}
try {
- if (event != null && !Context.getDataManager().getLastEvents(
- position.getDeviceId(), event.getType(), suppressRepeated).isEmpty()) {
- event = null;
+ if (result != null && !result.isEmpty()) {
+ for (Event event : result) {
+ if (!Context.getDataManager().getLastEvents(position.getDeviceId(),
+ event.getType(), suppressRepeated).isEmpty()) {
+ event = null;
+ }
+ }
}
-
} catch (SQLException error) {
Log.warning(error);
}
- return event;
+ return result;
}
}
diff --git a/src/org/traccar/events/OverspeedEventHandler.java b/src/org/traccar/events/OverspeedEventHandler.java
index 30410ff32..e14d4bcea 100644
--- a/src/org/traccar/events/OverspeedEventHandler.java
+++ b/src/org/traccar/events/OverspeedEventHandler.java
@@ -1,9 +1,27 @@
+/*
+ * Copyright 2016 Anton Tananaev (anton.tananaev@gmail.com)
+ *
+ * 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.traccar.events;
import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Collection;
import org.traccar.BaseEventHandler;
import org.traccar.Context;
+import org.traccar.model.Device;
import org.traccar.model.Event;
import org.traccar.model.Position;
import org.traccar.helper.Log;
@@ -20,11 +38,17 @@ public class OverspeedEventHandler extends BaseEventHandler {
}
@Override
- protected Event analizePosition(Position position) {
- Event event = null;
- if (!isLastPosition()) {
- return event;
+ protected Collection<Event> analyzePosition(Position position) {
+
+ Device device = Context.getDataManager().getDeviceById(position.getDeviceId());
+ if (device == null) {
+ return null;
+ }
+ if (position.getId() != device.getPositionId() || !position.getValid()) {
+ return null;
}
+
+ Collection<Event> events = new ArrayList<>();
double speed = position.getSpeed();
boolean valid = position.getValid();
@@ -32,14 +56,14 @@ public class OverspeedEventHandler extends BaseEventHandler {
try {
if (Context.getDataManager().getLastEvents(
position.getDeviceId(), Event.TYPE_DEVICE_OVERSPEED, suppressRepeated).isEmpty()) {
- event = new Event(Event.TYPE_DEVICE_OVERSPEED, position.getDeviceId(), position.getId());
+ events.add(new Event(Event.TYPE_DEVICE_OVERSPEED, position.getDeviceId(), position.getId()));
}
} catch (SQLException error) {
Log.warning(error);
}
}
- return event;
+ return events;
}
}
diff --git a/src/org/traccar/geofence/GeofenceCircle.java b/src/org/traccar/geofence/GeofenceCircle.java
new file mode 100644
index 000000000..a36620aec
--- /dev/null
+++ b/src/org/traccar/geofence/GeofenceCircle.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2016 Anton Tananaev (anton.tananaev@gmail.com)
+ *
+ * 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.traccar.geofence;
+
+import java.text.DecimalFormat;
+import java.text.ParseException;
+
+import org.traccar.helper.DistanceCalculator;
+
+public class GeofenceCircle extends GeofenceGeometry {
+
+ private double centerLatitude;
+ private double centerLongitude;
+ private double radius;
+
+ public GeofenceCircle() {
+ }
+
+ public GeofenceCircle(String wkt) throws ParseException {
+ fromWkt(wkt);
+ }
+
+ public GeofenceCircle(double latitude, double longitude, double radius) {
+ this.centerLatitude = latitude;
+ this.centerLongitude = longitude;
+ this.radius = radius;
+ }
+
+ @Override
+ public boolean containsPoint(double latitude, double longitude) {
+ return DistanceCalculator.distance(centerLatitude, centerLongitude, latitude, longitude) <= radius;
+ }
+
+ @Override
+ public String toWkt() {
+ String wkt = "";
+ wkt = "CIRCLE (";
+ wkt += String.valueOf(centerLatitude);
+ wkt += " ";
+ wkt += String.valueOf(centerLongitude);
+ wkt += ", ";
+ DecimalFormat format = new DecimalFormat("0.#");
+ wkt += format.format(radius);
+ wkt += ")";
+ return wkt;
+ }
+
+ @Override
+ public void fromWkt(String wkt) throws ParseException {
+ if (!wkt.startsWith("CIRCLE")) {
+ throw new ParseException("Mismatch geometry type", 0);
+ }
+ String content = wkt.substring(wkt.indexOf("(") + 1, wkt.indexOf(")"));
+ if (content == null || content.equals("")) {
+ throw new ParseException("No content", 0);
+ }
+ String[] commaTokens = content.split(",");
+ if (commaTokens.length != 2) {
+ throw new ParseException("Not valid content", 0);
+ }
+ String[] tokens = commaTokens[0].split("\\s");
+ if (tokens.length != 2) {
+ throw new ParseException("Too much or less coordinates", 0);
+ }
+ try {
+ centerLatitude = Double.parseDouble(tokens[0]);
+ } catch (NumberFormatException e) {
+ throw new ParseException(tokens[0] + " is not a double", 0);
+ }
+ try {
+ centerLongitude = Double.parseDouble(tokens[1]);
+ } catch (NumberFormatException e) {
+ throw new ParseException(tokens[1] + " is not a double", 0);
+ }
+ try {
+ radius = Double.parseDouble(commaTokens[1]);
+ } catch (NumberFormatException e) {
+ throw new ParseException(commaTokens[1] + " is not a double", 0);
+ }
+ }
+}
diff --git a/src/org/traccar/geofence/GeofenceGeometry.java b/src/org/traccar/geofence/GeofenceGeometry.java
new file mode 100644
index 000000000..e717ede0b
--- /dev/null
+++ b/src/org/traccar/geofence/GeofenceGeometry.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2016 Anton Tananaev (anton.tananaev@gmail.com)
+ *
+ * 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.traccar.geofence;
+
+import java.text.ParseException;
+
+public abstract class GeofenceGeometry {
+
+ public abstract boolean containsPoint(double latitude, double longitude);
+
+ public abstract String toWkt();
+
+ public abstract void fromWkt(String wkt) throws ParseException;
+
+}
diff --git a/src/org/traccar/geofence/GeofencePolygon.java b/src/org/traccar/geofence/GeofencePolygon.java
new file mode 100644
index 000000000..be5f971a5
--- /dev/null
+++ b/src/org/traccar/geofence/GeofencePolygon.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright 2016 Anton Tananaev (anton.tananaev@gmail.com)
+ *
+ * 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.traccar.geofence;
+
+import java.text.ParseException;
+import java.util.ArrayList;
+
+public class GeofencePolygon extends GeofenceGeometry {
+
+ public GeofencePolygon() {
+ }
+
+ public GeofencePolygon(String wkt) throws ParseException {
+ fromWkt(wkt);
+ }
+
+ private static class Coordinate {
+
+ public static final double DEGREE360 = 360;
+
+ private double lat;
+ private double lon;
+
+ public double getLat() {
+ return lat;
+ }
+
+ public void setLat(double lat) {
+ this.lat = lat;
+ }
+
+ public double getLon() {
+ return lon;
+ }
+
+ // Need not to confuse algorithm by the abrupt reset of longitude
+ public double getLon360() {
+ return lon + DEGREE360;
+ }
+
+ public void setLon(double lon) {
+ this.lon = lon;
+ }
+ }
+
+ private ArrayList<Coordinate> coordinates;
+
+ private double[] constant;
+ private double[] multiple;
+
+ private void precalc() {
+ if (coordinates == null) {
+ return;
+ }
+ int polyCorners = coordinates.size();
+ int i;
+ int j = polyCorners - 1;
+
+ if (constant != null) {
+ constant = null;
+ }
+ if (multiple != null) {
+ multiple = null;
+ }
+
+ constant = new double[polyCorners];
+ multiple = new double[polyCorners];
+
+
+ for (i = 0; i < polyCorners; j = i++) {
+ if (coordinates.get(j).getLon360() == coordinates.get(i).getLon360()) {
+ constant[i] = coordinates.get(i).getLat();
+ multiple[i] = 0;
+ } else {
+ constant[i] = coordinates.get(i).getLat()
+ - (coordinates.get(i).getLon360() * coordinates.get(j).getLat())
+ / (coordinates.get(j).getLon360() - coordinates.get(i).getLon360())
+ + (coordinates.get(i).getLon360() * coordinates.get(i).getLat())
+ / (coordinates.get(j).getLon360() - coordinates.get(i).getLon360());
+ multiple[i] = (coordinates.get(j).getLat() - coordinates.get(i).getLat())
+ / (coordinates.get(j).getLon360() - coordinates.get(i).getLon360());
+ }
+ }
+ }
+
+ @Override
+ public boolean containsPoint(double latitude, double longitude) {
+
+ int polyCorners = coordinates.size();
+ int i;
+ int j = polyCorners - 1;
+ double longitude360 = longitude + Coordinate.DEGREE360;
+ boolean oddNodes = false;
+
+ for (i = 0; i < polyCorners; j = i++) {
+ if (coordinates.get(i).getLon360() < longitude360
+ && coordinates.get(j).getLon360() >= longitude360
+ || coordinates.get(j).getLon360() < longitude360
+ && coordinates.get(i).getLon360() >= longitude360) {
+ oddNodes ^= longitude360 * multiple[i] + constant[i] < latitude;
+ }
+ }
+ return oddNodes;
+ }
+
+ @Override
+ public String toWkt() {
+ StringBuffer buf = new StringBuffer();
+ buf.append("POLYGON (");
+ for (Coordinate coordinate : coordinates) {
+ buf.append(String.valueOf(coordinate.getLat()));
+ buf.append(" ");
+ buf.append(String.valueOf(coordinate.getLon()));
+ buf.append(", ");
+ }
+ return buf.substring(0, buf.length() - 2) + ")";
+ }
+
+ @Override
+ public void fromWkt(String wkt) throws ParseException {
+ if (coordinates == null) {
+ coordinates = new ArrayList<Coordinate>();
+ } else {
+ coordinates.clear();
+ }
+
+ if (!wkt.startsWith("POLYGON")) {
+ throw new ParseException("Mismatch geometry type", 0);
+ }
+ String content = wkt.substring(wkt.indexOf("(") + 1, wkt.indexOf(")"));
+ if (content == null || content.equals("")) {
+ throw new ParseException("No content", 0);
+ }
+ String[] commaTokens = content.split(",");
+ if (commaTokens.length < 3) {
+ throw new ParseException("Not valid content", 0);
+ }
+
+ for (String commaToken : commaTokens) {
+ String[] tokens = commaToken.trim().split("\\s");
+ if (tokens.length != 2) {
+ throw new ParseException("Here must be two coordinates: " + commaToken, 0);
+ }
+ Coordinate coordinate = new Coordinate();
+ try {
+ coordinate.setLat(Double.parseDouble(tokens[0]));
+ } catch (NumberFormatException e) {
+ throw new ParseException(tokens[0] + " is not a double", 0);
+ }
+ try {
+ coordinate.setLon(Double.parseDouble(tokens[1]));
+ } catch (NumberFormatException e) {
+ throw new ParseException(tokens[1] + " is not a double", 0);
+ }
+ coordinates.add(coordinate);
+ }
+ precalc();
+ }
+
+}
diff --git a/src/org/traccar/model/Device.java b/src/org/traccar/model/Device.java
index d32f9f851..c42eb3718 100644
--- a/src/org/traccar/model/Device.java
+++ b/src/org/traccar/model/Device.java
@@ -16,6 +16,7 @@
package org.traccar.model;
import java.util.Date;
+import java.util.List;
public class Device {
@@ -114,4 +115,13 @@ public class Device {
this.motion = motion;
}
+ private List<Long> geofenceIds;
+
+ public List<Long> getGeofenceIds() {
+ return geofenceIds;
+ }
+
+ public void setGeofenceIds(List<Long> geofenceIds) {
+ this.geofenceIds = geofenceIds;
+ }
}
diff --git a/src/org/traccar/model/DeviceGeofence.java b/src/org/traccar/model/DeviceGeofence.java
new file mode 100644
index 000000000..05f06bb3d
--- /dev/null
+++ b/src/org/traccar/model/DeviceGeofence.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2016 Anton Tananaev (anton.tananaev@gmail.com)
+ *
+ * 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.traccar.model;
+
+public class DeviceGeofence {
+
+ private long deviceId;
+
+ public long getDeviceId() {
+ return deviceId;
+ }
+
+ public void setDeviceId(long deviceId) {
+ this.deviceId = deviceId;
+ }
+
+ private long geofenceId;
+
+ public long getGeofenceId() {
+ return geofenceId;
+ }
+
+ public void setGeofenceId(long geofenceId) {
+ this.geofenceId = geofenceId;
+ }
+
+}
diff --git a/src/org/traccar/model/Event.java b/src/org/traccar/model/Event.java
index 6de885c70..235a39f9a 100644
--- a/src/org/traccar/model/Event.java
+++ b/src/org/traccar/model/Event.java
@@ -1,3 +1,18 @@
+/*
+ * Copyright 2016 Anton Tananaev (anton.tananaev@gmail.com)
+ *
+ * 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.traccar.model;
import java.util.Date;
@@ -20,16 +35,6 @@ public class Event extends Message {
public Event() {
}
- private long id;
-
- public long getId() {
- return id;
- }
-
- public void setId(long id) {
- this.id = id;
- }
-
public static final String TYPE_COMMAND_RESULT = "commandResult";
public static final String TYPE_DEVICE_ONLINE = "deviceOnline";
@@ -71,4 +76,14 @@ public class Event extends Message {
this.positionId = positionId;
}
+ private long geofenceId = 0;
+
+ public long getGeofenceId() {
+ return geofenceId;
+ }
+
+ public void setGeofenceId(long geofenceId) {
+ this.geofenceId = geofenceId;
+ }
+
}
diff --git a/src/org/traccar/model/Extensible.java b/src/org/traccar/model/Extensible.java
new file mode 100644
index 000000000..eceeccadf
--- /dev/null
+++ b/src/org/traccar/model/Extensible.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2016 Anton Tananaev (anton.tananaev@gmail.com)
+ *
+ * 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.traccar.model;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+public class Extensible {
+
+ private long id;
+
+ public long getId() {
+ return id;
+ }
+
+ public void setId(long id) {
+ this.id = id;
+ }
+
+ private Map<String, Object> attributes = new LinkedHashMap<>();
+
+ public Map<String, Object> getAttributes() {
+ return attributes;
+ }
+
+ public void setAttributes(Map<String, Object> attributes) {
+ this.attributes = attributes;
+ }
+
+ public void set(String key, boolean value) {
+ attributes.put(key, value);
+ }
+
+ public void set(String key, int value) {
+ attributes.put(key, value);
+ }
+
+ public void set(String key, long value) {
+ attributes.put(key, value);
+ }
+
+ public void set(String key, double value) {
+ attributes.put(key, value);
+ }
+
+ public void set(String key, String value) {
+ if (value != null && !value.isEmpty()) {
+ attributes.put(key, value);
+ }
+ }
+
+ public void add(Map.Entry<String, Object> entry) {
+ if (entry != null && entry.getValue() != null) {
+ attributes.put(entry.getKey(), entry.getValue());
+ }
+ }
+
+}
diff --git a/src/org/traccar/model/Geofence.java b/src/org/traccar/model/Geofence.java
new file mode 100644
index 000000000..9a60f784f
--- /dev/null
+++ b/src/org/traccar/model/Geofence.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2016 Anton Tananaev (anton.tananaev@gmail.com)
+ *
+ * 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.traccar.model;
+
+import java.text.ParseException;
+
+import org.traccar.geofence.GeofenceCircle;
+import org.traccar.geofence.GeofenceGeometry;
+import org.traccar.geofence.GeofencePolygon;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+
+public class Geofence extends Extensible {
+
+ public static final String TYPE_GEOFENCE_CILCLE = "geofenceCircle";
+ public static final String TYPE_GEOFENCE_POLYGON = "geofencePolygon";
+
+ private String name;
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ private String description;
+
+ public String getDescription() {
+ return description;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ private String area;
+
+ public String getArea() {
+ return area;
+ }
+
+ public void setArea(String area) throws ParseException {
+
+ if (area.startsWith("CIRCLE")) {
+ geometry = new GeofenceCircle(area);
+ } else if (area.startsWith("POLYGON")) {
+ geometry = new GeofencePolygon(area);
+ } else {
+ throw new ParseException("Unknown geometry type", 0);
+ }
+
+ this.area = area;
+ }
+
+ private GeofenceGeometry geometry;
+
+ @JsonIgnore
+ public GeofenceGeometry getGeometry() {
+ return geometry;
+ }
+
+ public void setGeometry(GeofenceGeometry geometry) {
+ area = geometry.toWkt();
+ this.geometry = geometry;
+ }
+
+}
diff --git a/src/org/traccar/model/GeofencePermission.java b/src/org/traccar/model/GeofencePermission.java
new file mode 100644
index 000000000..269918d66
--- /dev/null
+++ b/src/org/traccar/model/GeofencePermission.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2016 Anton Tananaev (anton.tananaev@gmail.com)
+ *
+ * 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.traccar.model;
+
+public class GeofencePermission {
+
+ private long userId;
+
+ public long getUserId() {
+ return userId;
+ }
+
+ public void setUserId(long userId) {
+ this.userId = userId;
+ }
+
+ private long geofenceId;
+
+ public long getGeofenceId() {
+ return geofenceId;
+ }
+
+ public void setGeofenceId(long geofenceId) {
+ this.geofenceId = geofenceId;
+ }
+
+}
diff --git a/src/org/traccar/model/GroupGeofence.java b/src/org/traccar/model/GroupGeofence.java
new file mode 100644
index 000000000..0e261fd54
--- /dev/null
+++ b/src/org/traccar/model/GroupGeofence.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2016 Anton Tananaev (anton.tananaev@gmail.com)
+ *
+ * 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.traccar.model;
+
+public class GroupGeofence {
+
+ private long groupId;
+
+ public long getGroupId() {
+ return groupId;
+ }
+
+ public void setGroupId(long groupId) {
+ this.groupId = groupId;
+ }
+
+ private long geofenceId;
+
+ public long getGeofenceId() {
+ return geofenceId;
+ }
+
+ public void setGeofenceId(long geofenceId) {
+ this.geofenceId = geofenceId;
+ }
+
+}
diff --git a/src/org/traccar/model/Message.java b/src/org/traccar/model/Message.java
index 8722acc16..55d9fd0c7 100644
--- a/src/org/traccar/model/Message.java
+++ b/src/org/traccar/model/Message.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2013 - 2015 Anton Tananaev (anton.tananaev@gmail.com)
+ * Copyright 2013 - 2016 Anton Tananaev (anton.tananaev@gmail.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,10 +15,7 @@
*/
package org.traccar.model;
-import java.util.LinkedHashMap;
-import java.util.Map;
-
-public class Message {
+public class Message extends Extensible {
private long deviceId;
@@ -40,42 +37,4 @@ public class Message {
this.type = type;
}
- private Map<String, Object> attributes = new LinkedHashMap<>();
-
- public Map<String, Object> getAttributes() {
- return attributes;
- }
-
- public void setAttributes(Map<String, Object> attributes) {
- this.attributes = attributes;
- }
-
- public void set(String key, boolean value) {
- attributes.put(key, value);
- }
-
- public void set(String key, int value) {
- attributes.put(key, value);
- }
-
- public void set(String key, long value) {
- attributes.put(key, value);
- }
-
- public void set(String key, double value) {
- attributes.put(key, value);
- }
-
- public void set(String key, String value) {
- if (value != null && !value.isEmpty()) {
- attributes.put(key, value);
- }
- }
-
- public void add(Map.Entry<String, Object> entry) {
- if (entry != null && entry.getValue() != null) {
- attributes.put(entry.getKey(), entry.getValue());
- }
- }
-
}
diff --git a/src/org/traccar/model/Position.java b/src/org/traccar/model/Position.java
index 22d1be846..4e03b2097 100644
--- a/src/org/traccar/model/Position.java
+++ b/src/org/traccar/model/Position.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012 - 2015 Anton Tananaev (anton.tananaev@gmail.com)
+ * Copyright 2012 - 2016 Anton Tananaev (anton.tananaev@gmail.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -66,16 +66,6 @@ public class Position extends Message {
public static final String PREFIX_IO = "io";
public static final String PREFIX_COUNT = "count";
- private long id;
-
- public long getId() {
- return id;
- }
-
- public void setId(long id) {
- this.id = id;
- }
-
private String protocol;
public String getProtocol() {
diff --git a/src/org/traccar/web/WebServer.java b/src/org/traccar/web/WebServer.java
index 751db7a33..c06ee5d35 100644
--- a/src/org/traccar/web/WebServer.java
+++ b/src/org/traccar/web/WebServer.java
@@ -44,7 +44,11 @@ import org.traccar.api.resource.GroupResource;
import org.traccar.api.resource.DeviceResource;
import org.traccar.api.resource.PositionResource;
import org.traccar.api.resource.CommandTypeResource;
+import org.traccar.api.resource.DeviceGeofenceResource;
import org.traccar.api.resource.EventResource;
+import org.traccar.api.resource.GeofencePermissionResource;
+import org.traccar.api.resource.GeofenceResource;
+import org.traccar.api.resource.GroupGeofenceResource;
import org.traccar.helper.Log;
import javax.naming.InitialContext;
@@ -150,7 +154,8 @@ public class WebServer {
resourceConfig.registerClasses(ServerResource.class, SessionResource.class, CommandResource.class,
GroupPermissionResource.class, DevicePermissionResource.class, UserResource.class,
GroupResource.class, DeviceResource.class, PositionResource.class,
- CommandTypeResource.class, EventResource.class);
+ CommandTypeResource.class, EventResource.class, GeofenceResource.class,
+ DeviceGeofenceResource.class, GeofencePermissionResource.class, GroupGeofenceResource.class);
servletHandler.addServlet(new ServletHolder(new ServletContainer(resourceConfig)), "/*");
handlers.addHandler(servletHandler);
diff --git a/test/org/traccar/geofence/GeofenceCircleTest.java b/test/org/traccar/geofence/GeofenceCircleTest.java
new file mode 100644
index 000000000..52c214b53
--- /dev/null
+++ b/test/org/traccar/geofence/GeofenceCircleTest.java
@@ -0,0 +1,36 @@
+package org.traccar.geofence;
+
+import java.text.ParseException;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class GeofenceCircleTest {
+
+ @Test
+ public void testCircleWKT() {
+ String test = "CIRCLE (55.75414 37.6204, 100)";
+ GeofenceGeometry geofenceGeometry = new GeofenceCircle();
+ try {
+ geofenceGeometry.fromWkt(test);
+ } catch (ParseException e){
+ Assert.assertTrue("ParseExceprion: " + e.getMessage(), true);
+ }
+ Assert.assertEquals(geofenceGeometry.toWkt(), test);
+ }
+
+ @Test
+ public void testContainsCircle() {
+ String test = "CIRCLE (55.75414 37.6204, 100)";
+ GeofenceGeometry geofenceGeometry = new GeofenceCircle();
+ try {
+ geofenceGeometry.fromWkt(test);
+ } catch (ParseException e){
+ Assert.assertTrue("ParseExceprion: " + e.getMessage(), true);
+ }
+
+ Assert.assertTrue(geofenceGeometry.containsPoint(55.75477, 37.62025));
+
+ Assert.assertTrue(!geofenceGeometry.containsPoint(55.75545, 37.61921));
+ }
+}
diff --git a/test/org/traccar/geofence/GeofencePolygonTest.java b/test/org/traccar/geofence/GeofencePolygonTest.java
new file mode 100644
index 000000000..8711a3696
--- /dev/null
+++ b/test/org/traccar/geofence/GeofencePolygonTest.java
@@ -0,0 +1,38 @@
+package org.traccar.geofence;
+
+import java.text.ParseException;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class GeofencePolygonTest {
+
+ @Test
+ public void testPolygonWKT() {
+ String test = "POLYGON (55.75474 37.61823, 55.75513 37.61888, 55.7535 37.6222, 55.75315 37.62165)";
+ GeofenceGeometry geofenceGeometry = new GeofencePolygon();
+ try {
+ geofenceGeometry.fromWkt(test);
+ } catch (ParseException e){
+ Assert.assertTrue("ParseExceprion: " + e.getMessage(), true);
+ }
+ Assert.assertEquals(geofenceGeometry.toWkt(), test);
+ }
+
+ @Test
+ public void testContainsPolygon() {
+ String test = "POLYGON (55.75474 37.61823, 55.75513 37.61888, 55.7535 37.6222, 55.75315 37.62165)";
+ GeofenceGeometry geofenceGeometry = new GeofencePolygon();
+ try {
+ geofenceGeometry.fromWkt(test);
+ } catch (ParseException e){
+ Assert.assertTrue("ParseExceprion: " + e.getMessage(), true);
+ }
+
+ Assert.assertTrue(geofenceGeometry.containsPoint(55.75476, 37.61915));
+
+ Assert.assertTrue(!geofenceGeometry.containsPoint(55.75545, 37.61921));
+
+ }
+
+}
diff --git a/web/l10n/en.json b/web/l10n/en.json
index f4bd253ea..48dc6533c 100644
--- a/web/l10n/en.json
+++ b/web/l10n/en.json
@@ -105,5 +105,5 @@
"eventDeviceOverspeed": "Device exceeds the speed",
"eventCommandResult": "Command result: ",
"eventGeofenceEnter": "Device has entered geofence",
- "eventGeofenceEnter": "Device has exited geofence"
+ "eventGeofenceExit": "Device has exited geofence"
} \ No newline at end of file