diff options
-rw-r--r-- | src/org/traccar/api/resource/ReportResource.java | 27 | ||||
-rw-r--r-- | src/org/traccar/model/Position.java | 1 | ||||
-rw-r--r-- | src/org/traccar/protocol/OwnTracksProtocolDecoder.java | 27 | ||||
-rw-r--r-- | src/org/traccar/protocol/WatchProtocolDecoder.java | 4 | ||||
-rw-r--r-- | src/org/traccar/reports/ReportUtils.java | 200 | ||||
-rw-r--r-- | src/org/traccar/reports/Stops.java | 101 | ||||
-rw-r--r-- | src/org/traccar/reports/Trips.java | 136 | ||||
-rw-r--r-- | src/org/traccar/reports/model/BaseReport.java | 10 | ||||
-rw-r--r-- | src/org/traccar/reports/model/StopReport.java | 122 | ||||
-rw-r--r-- | src/org/traccar/reports/model/SummaryReport.java | 15 | ||||
-rw-r--r-- | src/org/traccar/reports/model/TripReport.java | 11 | ||||
-rw-r--r-- | src/org/traccar/reports/model/TripsConfig.java | 83 | ||||
-rw-r--r-- | templates/export/stops.xlsx | bin | 0 -> 12855 bytes | |||
-rw-r--r-- | test/org/traccar/protocol/Gps103ProtocolDecoderTest.java | 3 | ||||
-rw-r--r-- | test/org/traccar/protocol/OwnTracksProtocolDecoderTest.java | 6 | ||||
-rw-r--r-- | test/org/traccar/reports/ReportUtilsTest.java | 234 | ||||
-rw-r--r-- | test/org/traccar/reports/TripsTest.java | 70 |
17 files changed, 815 insertions, 235 deletions
diff --git a/src/org/traccar/api/resource/ReportResource.java b/src/org/traccar/api/resource/ReportResource.java index a0f686e80..7c472e84d 100644 --- a/src/org/traccar/api/resource/ReportResource.java +++ b/src/org/traccar/api/resource/ReportResource.java @@ -22,9 +22,11 @@ import org.traccar.model.Position; import org.traccar.reports.Events; import org.traccar.reports.Summary; import org.traccar.reports.Trips; +import org.traccar.reports.model.StopReport; import org.traccar.reports.model.SummaryReport; import org.traccar.reports.model.TripReport; import org.traccar.reports.Route; +import org.traccar.reports.Stops; @Path("reports") @Produces(MediaType.APPLICATION_JSON) @@ -129,4 +131,29 @@ public class ReportResource extends BaseResource { .header(HttpHeaders.CONTENT_DISPOSITION, CONTENT_DISPOSITION_VALUE_XLSX).build(); } + @Path("stops") + @GET + @Produces(MediaType.APPLICATION_JSON) + public Collection<StopReport> getStops( + @QueryParam("deviceId") final List<Long> deviceIds, @QueryParam("groupId") final List<Long> groupIds, + @QueryParam("from") String from, @QueryParam("to") String to) throws SQLException { + return Stops.getObjects(getUserId(), deviceIds, groupIds, + DateUtil.parseDate(from), DateUtil.parseDate(to)); + } + + @Path("stops") + @GET + @Produces(XLSX) + public Response getStopsExcel( + @QueryParam("deviceId") final List<Long> deviceIds, @QueryParam("groupId") final List<Long> groupIds, + @QueryParam("from") String from, @QueryParam("to") String to) throws SQLException, IOException { + ByteArrayOutputStream stream = new ByteArrayOutputStream(); + Stops.getExcel(stream, getUserId(), deviceIds, groupIds, + DateUtil.parseDate(from), DateUtil.parseDate(to)); + + return Response.ok(stream.toByteArray()) + .header(HttpHeaders.CONTENT_DISPOSITION, CONTENT_DISPOSITION_VALUE_XLSX).build(); + } + + } diff --git a/src/org/traccar/model/Position.java b/src/org/traccar/model/Position.java index c6420c17b..bf2518c3c 100644 --- a/src/org/traccar/model/Position.java +++ b/src/org/traccar/model/Position.java @@ -119,6 +119,7 @@ public class Position extends Message { public static final String ALARM_FOOT_BRAKE = "footBrake"; public static final String ALARM_OIL_LEAK = "oilLeak"; public static final String ALARM_TAMPERING = "tampering"; + public static final String ALARM_REMOVING = "removing"; private String protocol; diff --git a/src/org/traccar/protocol/OwnTracksProtocolDecoder.java b/src/org/traccar/protocol/OwnTracksProtocolDecoder.java index a5a9efbb4..ea604f4ac 100644 --- a/src/org/traccar/protocol/OwnTracksProtocolDecoder.java +++ b/src/org/traccar/protocol/OwnTracksProtocolDecoder.java @@ -1,6 +1,6 @@ /* * Copyright 2017 Jan-Piet Mens (jpmens@gmail.com) - * Copyright 2013 - 2017 Anton Tananaev (anton@traccar.org) + * Copyright 2017 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -58,6 +58,11 @@ public class OwnTracksProtocolDecoder extends BaseProtocolDecoder { JsonObject root = Json.createReader( new StringReader(request.getContent().toString(StandardCharsets.US_ASCII))).readObject(); + if (!root.containsKey("_type") || !root.getString("_type").equals("location")) { + sendResponse(channel, HttpResponseStatus.BAD_REQUEST); + return null; + } + Position position = new Position(); position.setProtocol(getProtocolName()); position.setValid(true); @@ -83,17 +88,21 @@ public class OwnTracksProtocolDecoder extends BaseProtocolDecoder { if (root.containsKey("batt")) { position.set(Position.KEY_BATTERY, root.getInt("batt")); } - if (root.containsKey("topic")) { - position.set("topic", root.getString("topic")); - } - long timestamp = root.getJsonNumber("tst").longValue(); - if (timestamp < Integer.MAX_VALUE) { - timestamp *= 1000; + position.setTime(new Date(root.getJsonNumber("tst").longValue() * 1000)); + + String uniqueId; + + if (root.containsKey("topic")) { + uniqueId = root.getString("topic"); + if (root.containsKey("tid")) { + position.set("tid", root.getString("tid")); + } + } else { + uniqueId = root.getString("tid"); } - position.setTime(new Date(timestamp)); - DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, root.getString("tid")); + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, uniqueId); if (deviceSession == null) { sendResponse(channel, HttpResponseStatus.BAD_REQUEST); return null; diff --git a/src/org/traccar/protocol/WatchProtocolDecoder.java b/src/org/traccar/protocol/WatchProtocolDecoder.java index 7af856a9c..d55c307da 100644 --- a/src/org/traccar/protocol/WatchProtocolDecoder.java +++ b/src/org/traccar/protocol/WatchProtocolDecoder.java @@ -76,6 +76,8 @@ public class WatchProtocolDecoder extends BaseProtocolDecoder { return Position.ALARM_GEOFENCE_ENTER; } else if (BitUtil.check(status, 3)) { return Position.ALARM_OVERSPEED; + } else if (BitUtil.check(status, 4)) { + return Position.ALARM_MOVEMENT; } else if (BitUtil.check(status, 16)) { return Position.ALARM_SOS; } else if (BitUtil.check(status, 17)) { @@ -84,6 +86,8 @@ public class WatchProtocolDecoder extends BaseProtocolDecoder { return Position.ALARM_GEOFENCE_EXIT; } else if (BitUtil.check(status, 19)) { return Position.ALARM_GEOFENCE_ENTER; + } else if (BitUtil.check(status, 20)) { + return Position.ALARM_REMOVING; } return null; } diff --git a/src/org/traccar/reports/ReportUtils.java b/src/org/traccar/reports/ReportUtils.java index 84d3b3a77..1e004beae 100644 --- a/src/org/traccar/reports/ReportUtils.java +++ b/src/org/traccar/reports/ReportUtils.java @@ -1,6 +1,6 @@ /* - * Copyright 2016 Anton Tananaev (anton@traccar.org) - * Copyright 2016 Andrey Kunitsyn (andrey@traccar.org) + * Copyright 2016 - 2017 Anton Tananaev (anton@traccar.org) + * Copyright 2016 - 2017 Andrey Kunitsyn (andrey@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,6 +27,10 @@ import org.jxls.transform.poi.PoiTransformer; import org.jxls.util.TransformerFactory; import org.traccar.Context; import org.traccar.model.Position; +import org.traccar.reports.model.BaseReport; +import org.traccar.reports.model.StopReport; +import org.traccar.reports.model.TripReport; +import org.traccar.reports.model.TripsConfig; import java.io.IOException; import java.io.InputStream; @@ -123,4 +127,196 @@ public final class ReportUtils { transformer.write(); } + public static TripsConfig initTripsConfig() { + return new TripsConfig( + Context.getConfig().getLong("report.trip.minimalTripDuration", 300) * 1000, + Context.getConfig().getLong("report.trip.minimalTripDistance", 500), + Context.getConfig().getLong("report.trip.minimalParkingDuration", 300) * 1000, + Context.getConfig().getBoolean("report.trip.greedyParking"), + Context.getConfig().getLong("report.trip.minimalNoDataDuration", 3600) * 1000); + } + + private static TripReport calculateTrip( + ArrayList<Position> positions, int startIndex, int endIndex, boolean ignoreOdometer) { + Position startTrip = positions.get(startIndex); + Position endTrip = positions.get(endIndex); + + double speedMax = 0.0; + double speedSum = 0.0; + for (int i = startIndex; i <= endIndex; i++) { + double speed = positions.get(i).getSpeed(); + speedSum += speed; + if (speed > speedMax) { + speedMax = speed; + } + } + + TripReport trip = new TripReport(); + + long tripDuration = endTrip.getFixTime().getTime() - startTrip.getFixTime().getTime(); + long deviceId = startTrip.getDeviceId(); + trip.setDeviceId(deviceId); + trip.setDeviceName(Context.getIdentityManager().getDeviceById(deviceId).getName()); + + trip.setStartPositionId(startTrip.getId()); + trip.setStartLat(startTrip.getLatitude()); + trip.setStartLon(startTrip.getLongitude()); + trip.setStartTime(startTrip.getFixTime()); + trip.setStartAddress(startTrip.getAddress()); + + trip.setEndPositionId(endTrip.getId()); + trip.setEndLat(endTrip.getLatitude()); + trip.setEndLon(endTrip.getLongitude()); + trip.setEndTime(endTrip.getFixTime()); + trip.setEndAddress(endTrip.getAddress()); + + trip.setDistance(calculateDistance(startTrip, endTrip, !ignoreOdometer)); + trip.setDuration(tripDuration); + trip.setAverageSpeed(speedSum / (endIndex - startIndex)); + trip.setMaxSpeed(speedMax); + trip.setSpentFuel(calculateFuel(startTrip, endTrip)); + + return trip; + } + + private static StopReport calculateStop(ArrayList<Position> positions, int startIndex, int endIndex) { + Position startStop = positions.get(startIndex); + Position endStop = positions.get(endIndex); + + StopReport stop = new StopReport(); + + long deviceId = startStop.getDeviceId(); + stop.setDeviceId(deviceId); + stop.setDeviceName(Context.getIdentityManager().getDeviceById(deviceId).getName()); + + stop.setPositionId(startStop.getId()); + stop.setLatitude(startStop.getLatitude()); + stop.setLongitude(startStop.getLongitude()); + stop.setStartTime(startStop.getFixTime()); + stop.setAddress(startStop.getAddress()); + stop.setEndTime(endStop.getFixTime()); + + long stopDuration = endStop.getFixTime().getTime() - startStop.getFixTime().getTime(); + stop.setDuration(stopDuration); + stop.setSpentFuel(calculateFuel(startStop, endStop)); + + long engineHours = 0; + for (int i = startIndex + 1; i <= endIndex; i++) { + if (positions.get(i).getBoolean(Position.KEY_IGNITION) + && positions.get(i - 1).getBoolean(Position.KEY_IGNITION)) { + engineHours += positions.get(i).getFixTime().getTime() - positions.get(i - 1).getFixTime().getTime(); + } + } + stop.setEngineHours(engineHours); + + return stop; + + } + + private static boolean isMoving(ArrayList<Position> positions, int index, + TripsConfig tripsConfig, double speedThreshold) { + if (tripsConfig.getMinimalNoDataDuration() > 0 && index < positions.size() - 1 + && positions.get(index + 1).getFixTime().getTime() - positions.get(index).getFixTime().getTime() + >= tripsConfig.getMinimalNoDataDuration()) { + return false; + } + return positions.get(index).getSpeed() > speedThreshold; + } + + public static Collection<BaseReport> detectTripsAndStops(TripsConfig tripsConfig, boolean ignoreOdometer, + double speedThreshold, Collection<Position> positionCollection, boolean trips) { + + Collection<BaseReport> result = new ArrayList<>(); + + ArrayList<Position> positions = new ArrayList<>(positionCollection); + if (positions != null && !positions.isEmpty()) { + int previousStartParkingIndex = 0; + int startParkingIndex = -1; + int previousEndParkingIndex = 0; + int endParkingIndex = 0; + + boolean isMoving = false; + boolean isLast = false; + boolean skipped = false; + boolean tripFiltered = false; + + for (int i = 0; i < positions.size(); i++) { + isMoving = isMoving(positions, i, tripsConfig, speedThreshold); + isLast = i == positions.size() - 1; + + if ((isMoving || isLast) && startParkingIndex != -1) { + if (!skipped || previousEndParkingIndex == 0) { + previousEndParkingIndex = endParkingIndex; + } + endParkingIndex = i; + } + if (!isMoving && startParkingIndex == -1) { + if (tripsConfig.getGreedyParking()) { + long tripDuration = positions.get(i).getFixTime().getTime() + - positions.get(endParkingIndex).getFixTime().getTime(); + double tripDistance = ReportUtils.calculateDistance(positions.get(endParkingIndex), + positions.get(i), false); + tripFiltered = tripDuration < tripsConfig.getMinimalTripDuration() + && tripDistance < tripsConfig.getMinimalTripDistance(); + if (tripFiltered) { + startParkingIndex = previousStartParkingIndex; + endParkingIndex = previousEndParkingIndex; + tripFiltered = false; + } else { + previousStartParkingIndex = i; + startParkingIndex = i; + } + } else { + long tripDuration = positions.get(i).getFixTime().getTime() + - positions.get(previousEndParkingIndex).getFixTime().getTime(); + double tripDistance = ReportUtils.calculateDistance(positions.get(previousEndParkingIndex), + positions.get(i), false); + tripFiltered = tripDuration < tripsConfig.getMinimalTripDuration() + && tripDistance < tripsConfig.getMinimalTripDistance(); + startParkingIndex = i; + } + } + if (startParkingIndex != -1 && (endParkingIndex > startParkingIndex || isLast)) { + long parkingDuration = positions.get(endParkingIndex).getFixTime().getTime() + - positions.get(startParkingIndex).getFixTime().getTime(); + if ((parkingDuration >= tripsConfig.getMinimalParkingDuration() || isLast) + && previousEndParkingIndex < startParkingIndex) { + if (!tripFiltered) { + if (trips) { + result.add(calculateTrip( + positions, previousEndParkingIndex, startParkingIndex, ignoreOdometer)); + } else { + if (result.isEmpty() && previousEndParkingIndex > previousStartParkingIndex) { + long previousParkingDuration = positions.get(previousEndParkingIndex) + .getFixTime().getTime() - positions.get(previousStartParkingIndex) + .getFixTime().getTime(); + if (previousParkingDuration >= tripsConfig.getMinimalParkingDuration()) { + result.add(calculateStop(positions, previousStartParkingIndex, + previousEndParkingIndex)); + } + } + result.add(calculateStop(positions, startParkingIndex, endParkingIndex)); + } + } + previousEndParkingIndex = endParkingIndex; + skipped = false; + } else { + skipped = true; + } + startParkingIndex = -1; + } + } + if (result.isEmpty() && !trips) { + int end = isMoving && !tripsConfig.getGreedyParking() + ? Math.max(endParkingIndex, previousEndParkingIndex) : positions.size() - 1; + long parkingDuration = positions.get(end).getFixTime().getTime() + - positions.get(previousStartParkingIndex).getFixTime().getTime(); + if (parkingDuration >= tripsConfig.getMinimalParkingDuration()) { + result.add(calculateStop(positions, previousStartParkingIndex, end)); + } + } + } + + return result; + } } diff --git a/src/org/traccar/reports/Stops.java b/src/org/traccar/reports/Stops.java new file mode 100644 index 000000000..64e589be1 --- /dev/null +++ b/src/org/traccar/reports/Stops.java @@ -0,0 +1,101 @@ +/* + * Copyright 2017 Anton Tananaev (anton@traccar.org) + * Copyright 2017 Andrey Kunitsyn (andrey@traccar.org) + * + * 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.reports; + +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; + +import org.apache.poi.ss.util.WorkbookUtil; +import org.traccar.Context; +import org.traccar.model.Device; +import org.traccar.model.Group; +import org.traccar.reports.model.BaseReport; +import org.traccar.reports.model.DeviceReport; +import org.traccar.reports.model.StopReport; +import org.traccar.reports.model.TripsConfig; + +public final class Stops { + + private Stops() { + } + + private static Collection<StopReport> detectStops(long deviceId, Date from, Date to) throws SQLException { + double speedThreshold = Context.getConfig().getDouble("event.motion.speedThreshold", 0.01); + + TripsConfig tripsConfig = ReportUtils.initTripsConfig(); + + boolean ignoreOdometer = Context.getDeviceManager() + .lookupAttributeBoolean(deviceId, "report.ignoreOdometer", false, true); + + Collection<? extends BaseReport> result = ReportUtils.detectTripsAndStops(tripsConfig, + ignoreOdometer, speedThreshold, + Context.getDataManager().getPositions(deviceId, from, to), false); + + return (Collection<StopReport>) result; + } + + public static Collection<StopReport> getObjects(long userId, Collection<Long> deviceIds, Collection<Long> groupIds, + Date from, Date to) throws SQLException { + ArrayList<StopReport> result = new ArrayList<>(); + for (long deviceId: ReportUtils.getDeviceList(deviceIds, groupIds)) { + Context.getPermissionsManager().checkDevice(userId, deviceId); + result.addAll(detectStops(deviceId, from, to)); + } + return result; + } + + public static void getExcel(OutputStream outputStream, + long userId, Collection<Long> deviceIds, Collection<Long> groupIds, + Date from, Date to) throws SQLException, IOException { + ArrayList<DeviceReport> devicesStops = new ArrayList<>(); + ArrayList<String> sheetNames = new ArrayList<>(); + for (long deviceId: ReportUtils.getDeviceList(deviceIds, groupIds)) { + Context.getPermissionsManager().checkDevice(userId, deviceId); + Collection<StopReport> stops = detectStops(deviceId, from, to); + DeviceReport deviceStops = new DeviceReport(); + Device device = Context.getIdentityManager().getDeviceById(deviceId); + deviceStops.setDeviceName(device.getName()); + sheetNames.add(WorkbookUtil.createSafeSheetName(deviceStops.getDeviceName())); + if (device.getGroupId() != 0) { + Group group = Context.getDeviceManager().getGroupById(device.getGroupId()); + if (group != null) { + deviceStops.setGroupName(group.getName()); + } + } + deviceStops.setObjects(stops); + devicesStops.add(deviceStops); + } + String templatePath = Context.getConfig().getString("report.templatesPath", + "templates/export/"); + try (InputStream inputStream = new FileInputStream(templatePath + "/stops.xlsx")) { + org.jxls.common.Context jxlsContext = ReportUtils.initializeContext(userId); + jxlsContext.putVar("devices", devicesStops); + jxlsContext.putVar("sheetNames", sheetNames); + jxlsContext.putVar("from", from); + jxlsContext.putVar("to", to); + ReportUtils.processTemplateWithSheets(inputStream, outputStream, jxlsContext); + } + } + +} diff --git a/src/org/traccar/reports/Trips.java b/src/org/traccar/reports/Trips.java index 966c3a817..5c26bea54 100644 --- a/src/org/traccar/reports/Trips.java +++ b/src/org/traccar/reports/Trips.java @@ -29,147 +29,29 @@ import org.apache.poi.ss.util.WorkbookUtil; import org.traccar.Context; import org.traccar.model.Device; import org.traccar.model.Group; -import org.traccar.model.Position; +import org.traccar.reports.model.BaseReport; import org.traccar.reports.model.DeviceReport; import org.traccar.reports.model.TripReport; +import org.traccar.reports.model.TripsConfig; public final class Trips { private Trips() { } - private static TripReport calculateTrip( - ArrayList<Position> positions, int startIndex, int endIndex, boolean ignoreOdometer) { - Position startTrip = positions.get(startIndex); - Position endTrip = positions.get(endIndex); - - double speedMax = 0.0; - double speedSum = 0.0; - for (int i = startIndex; i <= endIndex; i++) { - double speed = positions.get(i).getSpeed(); - speedSum += speed; - if (speed > speedMax) { - speedMax = speed; - } - } - - TripReport trip = new TripReport(); - - long tripDuration = endTrip.getFixTime().getTime() - startTrip.getFixTime().getTime(); - long deviceId = startTrip.getDeviceId(); - trip.setDeviceId(deviceId); - trip.setDeviceName(Context.getIdentityManager().getDeviceById(deviceId).getName()); - - trip.setStartPositionId(startTrip.getId()); - trip.setStartLat(startTrip.getLatitude()); - trip.setStartLon(startTrip.getLongitude()); - trip.setStartTime(startTrip.getFixTime()); - trip.setStartAddress(startTrip.getAddress()); - - trip.setEndPositionId(endTrip.getId()); - trip.setEndLat(endTrip.getLatitude()); - trip.setEndLon(endTrip.getLongitude()); - trip.setEndTime(endTrip.getFixTime()); - trip.setEndAddress(endTrip.getAddress()); - - trip.setDistance(ReportUtils.calculateDistance(startTrip, endTrip, !ignoreOdometer)); - trip.setDuration(tripDuration); - trip.setAverageSpeed(speedSum / (endIndex - startIndex)); - trip.setMaxSpeed(speedMax); - trip.setSpentFuel(ReportUtils.calculateFuel(startTrip, endTrip)); - - return trip; - } - - protected static Collection<TripReport> detectTrips( - double speedThreshold, double minimalTripDistance, - long minimalTripDuration, long minimalParkingDuration, boolean greedyParking, boolean ignoreOdometer, - Collection<Position> positionCollection) { - - Collection<TripReport> result = new ArrayList<>(); - - ArrayList<Position> positions = new ArrayList<>(positionCollection); - if (positions != null && !positions.isEmpty()) { - int previousStartParkingIndex = 0; - int startParkingIndex = -1; - int previousEndParkingIndex = 0; - int endParkingIndex = 0; - - boolean isMoving = false; - boolean isLast = false; - boolean skipped = false; - boolean tripFiltered = false; - - for (int i = 0; i < positions.size(); i++) { - isMoving = positions.get(i).getSpeed() > speedThreshold; - isLast = i == positions.size() - 1; - - if ((isMoving || isLast) && startParkingIndex != -1) { - if (!skipped || previousEndParkingIndex == 0) { - previousEndParkingIndex = endParkingIndex; - } - endParkingIndex = i; - } - if (!isMoving && startParkingIndex == -1) { - if (greedyParking) { - long tripDuration = positions.get(i).getFixTime().getTime() - - positions.get(endParkingIndex).getFixTime().getTime(); - double tripDistance = ReportUtils.calculateDistance(positions.get(endParkingIndex), - positions.get(i), false); - tripFiltered = tripDuration < minimalTripDuration && tripDistance < minimalTripDistance; - if (tripFiltered) { - startParkingIndex = previousStartParkingIndex; - endParkingIndex = previousEndParkingIndex; - tripFiltered = false; - } else { - previousStartParkingIndex = i; - startParkingIndex = i; - } - } else { - long tripDuration = positions.get(i).getFixTime().getTime() - - positions.get(previousEndParkingIndex).getFixTime().getTime(); - double tripDistance = ReportUtils.calculateDistance(positions.get(previousEndParkingIndex), - positions.get(i), false); - tripFiltered = tripDuration < minimalTripDuration && tripDistance < minimalTripDistance; - startParkingIndex = i; - } - } - if (startParkingIndex != -1 && (endParkingIndex > startParkingIndex || isLast)) { - long parkingDuration = positions.get(endParkingIndex).getFixTime().getTime() - - positions.get(startParkingIndex).getFixTime().getTime(); - if ((parkingDuration >= minimalParkingDuration || isLast) - && previousEndParkingIndex < startParkingIndex) { - if (!tripFiltered) { - result.add(calculateTrip( - positions, previousEndParkingIndex, startParkingIndex, ignoreOdometer)); - } - previousEndParkingIndex = endParkingIndex; - skipped = false; - } else { - skipped = true; - } - startParkingIndex = -1; - } - } - } - - return result; - } - private static Collection<TripReport> detectTrips(long deviceId, Date from, Date to) throws SQLException { double speedThreshold = Context.getConfig().getDouble("event.motion.speedThreshold", 0.01); - long minimalTripDuration = Context.getConfig().getLong("report.trip.minimalTripDuration", 300) * 1000; - double minimalTripDistance = Context.getConfig().getLong("report.trip.minimalTripDistance", 500); - long minimalParkingDuration = Context.getConfig().getLong("report.trip.minimalParkingDuration", 300) * 1000; - boolean greedyParking = Context.getConfig().getBoolean("report.trip.greedyParking"); + + TripsConfig tripsConfig = ReportUtils.initTripsConfig(); boolean ignoreOdometer = Context.getDeviceManager() .lookupAttributeBoolean(deviceId, "report.ignoreOdometer", false, true); - return detectTrips( - speedThreshold, minimalTripDistance, minimalTripDuration, - minimalParkingDuration, greedyParking, ignoreOdometer, - Context.getDataManager().getPositions(deviceId, from, to)); + Collection<? extends BaseReport> result = ReportUtils.detectTripsAndStops(tripsConfig, + ignoreOdometer, speedThreshold, + Context.getDataManager().getPositions(deviceId, from, to), true); + + return (Collection<TripReport>) result; } public static Collection<TripReport> getObjects(long userId, Collection<Long> deviceIds, Collection<Long> groupIds, diff --git a/src/org/traccar/reports/model/BaseReport.java b/src/org/traccar/reports/model/BaseReport.java index 13c67cbb7..e1dec1407 100644 --- a/src/org/traccar/reports/model/BaseReport.java +++ b/src/org/traccar/reports/model/BaseReport.java @@ -74,4 +74,14 @@ public class BaseReport { } } + private String spentFuel; + + public String getSpentFuel() { + return spentFuel; + } + + public void setSpentFuel(String spentFuel) { + this.spentFuel = spentFuel; + } + } diff --git a/src/org/traccar/reports/model/StopReport.java b/src/org/traccar/reports/model/StopReport.java new file mode 100644 index 000000000..6b2e86299 --- /dev/null +++ b/src/org/traccar/reports/model/StopReport.java @@ -0,0 +1,122 @@ +/* + * Copyright 2017 Anton Tananaev (anton@traccar.org) + * Copyright 2017 Andrey Kunitsyn (andrey@traccar.org) + * + * 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.reports.model; + +import java.util.Date; + +public class StopReport extends BaseReport { + + private long positionId; + + public long getPositionId() { + return positionId; + } + + public void setPositionId(long positionId) { + this.positionId = positionId; + } + + private double latitude; + + public double getLatitude() { + return latitude; + } + + public void setLatitude(double latitude) { + this.latitude = latitude; + } + + private double longitude; + + public double getLongitude() { + return longitude; + } + + public void setLongitude(double longitude) { + this.longitude = longitude; + } + + private Date startTime; + + public Date getStartTime() { + if (startTime != null) { + return new Date(startTime.getTime()); + } else { + return null; + } + } + + public void setStartTime(Date startTime) { + if (startTime != null) { + this.startTime = new Date(startTime.getTime()); + } else { + this.startTime = null; + } + } + + private Date endTime; + + public Date getEndTime() { + if (endTime != null) { + return new Date(endTime.getTime()); + } else { + return null; + } + } + + public void setEndTime(Date endTime) { + if (endTime != null) { + this.endTime = new Date(endTime.getTime()); + } else { + this.endTime = null; + } + } + + private String address; + + public String getAddress() { + return address; + } + + public void setAddress(String address) { + this.address = address; + } + + private long duration; + + public long getDuration() { + return duration; + } + + public void setDuration(long duration) { + this.duration = duration; + } + + private long engineHours; // milliseconds + + public long getEngineHours() { + return engineHours; + } + + public void setEngineHours(long engineHours) { + this.engineHours = engineHours; + } + + public void addEngineHours(long engineHours) { + this.engineHours += engineHours; + } +} diff --git a/src/org/traccar/reports/model/SummaryReport.java b/src/org/traccar/reports/model/SummaryReport.java index 85ba6bc54..6f9e9459f 100644 --- a/src/org/traccar/reports/model/SummaryReport.java +++ b/src/org/traccar/reports/model/SummaryReport.java @@ -1,6 +1,6 @@ /* - * Copyright 2016 Anton Tananaev (anton@traccar.org) - * Copyright 2016 Andrey Kunitsyn (andrey@traccar.org) + * Copyright 2016 - 2017 Anton Tananaev (anton@traccar.org) + * Copyright 2016 - 2017 Andrey Kunitsyn (andrey@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,15 +31,4 @@ public class SummaryReport extends BaseReport { public void addEngineHours(long engineHours) { this.engineHours += engineHours; } - - private String spentFuel; - - public String getSpentFuel() { - return spentFuel; - } - - public void setSpentFuel(String spentFuel) { - this.spentFuel = spentFuel; - } - } diff --git a/src/org/traccar/reports/model/TripReport.java b/src/org/traccar/reports/model/TripReport.java index fce805ad6..efe556f79 100644 --- a/src/org/traccar/reports/model/TripReport.java +++ b/src/org/traccar/reports/model/TripReport.java @@ -145,15 +145,4 @@ public class TripReport extends BaseReport { public void setDuration(long duration) { this.duration = duration; } - - private String spentFuel; - - public String getSpentFuel() { - return spentFuel; - } - - public void setSpentFuel(String spentFuel) { - this.spentFuel = spentFuel; - } - } diff --git a/src/org/traccar/reports/model/TripsConfig.java b/src/org/traccar/reports/model/TripsConfig.java new file mode 100644 index 000000000..7067781d7 --- /dev/null +++ b/src/org/traccar/reports/model/TripsConfig.java @@ -0,0 +1,83 @@ +/* + * Copyright 2017 Anton Tananaev (anton@traccar.org) + * Copyright 2017 Andrey Kunitsyn (andrey@traccar.org) + * + * 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.reports.model; + +public class TripsConfig { + + public TripsConfig() { + } + + public TripsConfig(double minimalTripDistance, long minimalTripDuration, + long minimalParkingDuration, boolean greedyParking, long minimalNoDataDuration) { + this.minimalTripDistance = minimalTripDistance; + this.minimalTripDuration = minimalTripDuration; + this.minimalParkingDuration = minimalParkingDuration; + this.greedyParking = greedyParking; + this.minimalNoDataDuration = minimalNoDataDuration; + } + + private double minimalTripDistance; + + public double getMinimalTripDistance() { + return minimalTripDistance; + } + + public void setMinimalTripDistance(double minimalTripDistance) { + this.minimalTripDistance = minimalTripDistance; + } + + private long minimalTripDuration; + + public long getMinimalTripDuration() { + return minimalTripDuration; + } + + public void setMinimalTripDuration(long minimalTripDuration) { + this.minimalTripDuration = minimalTripDuration; + } + + private long minimalParkingDuration; + + public long getMinimalParkingDuration() { + return minimalParkingDuration; + } + + public void setMinimalParkingDuration(long minimalParkingDuration) { + this.minimalParkingDuration = minimalParkingDuration; + } + + private boolean greedyParking; + + public boolean getGreedyParking() { + return greedyParking; + } + + public void setGreedyParking(boolean greedyParking) { + this.greedyParking = greedyParking; + } + + private long minimalNoDataDuration; + + public long getMinimalNoDataDuration() { + return minimalNoDataDuration; + } + + public void setMinimalNoDataDuration(long minimalNoDataDuration) { + this.minimalNoDataDuration = minimalNoDataDuration; + } + +} diff --git a/templates/export/stops.xlsx b/templates/export/stops.xlsx Binary files differnew file mode 100644 index 000000000..d6bb4c3b9 --- /dev/null +++ b/templates/export/stops.xlsx diff --git a/test/org/traccar/protocol/Gps103ProtocolDecoderTest.java b/test/org/traccar/protocol/Gps103ProtocolDecoderTest.java index 345a89ad9..ee48ebb27 100644 --- a/test/org/traccar/protocol/Gps103ProtocolDecoderTest.java +++ b/test/org/traccar/protocol/Gps103ProtocolDecoderTest.java @@ -10,6 +10,9 @@ public class Gps103ProtocolDecoderTest extends ProtocolTest { Gps103ProtocolDecoder decoder = new Gps103ProtocolDecoder(new Gps103Protocol()); + verifyNull(decoder, text( + "imei:868683027825532,OBD,170613203014,,,,0,0,0.00%,0,0.00%,0,0.00,,,,;")); + verifyAttributes(decoder, text( "imei:862106025092216,OBD,170612165656,196043,,145803.9,,000,0.0%,+,0.0%,00000,12.6,,,,;")); diff --git a/test/org/traccar/protocol/OwnTracksProtocolDecoderTest.java b/test/org/traccar/protocol/OwnTracksProtocolDecoderTest.java index 1eb188421..89b994fbe 100644 --- a/test/org/traccar/protocol/OwnTracksProtocolDecoderTest.java +++ b/test/org/traccar/protocol/OwnTracksProtocolDecoderTest.java @@ -12,11 +12,13 @@ public class OwnTracksProtocolDecoderTest extends ProtocolTest { OwnTracksProtocolDecoder decoder = new OwnTracksProtocolDecoder(new OwnTracksProtocol()); verifyPosition(decoder, request(HttpMethod.POST, "/", - buffer(text("{\"lon\":2.29513,\"lat\":48.85833,\"tst\":1497349316,\"_type\":\"location\",\"tid\":\"JJ\"}")))); + buffer("{\"lon\":2.29513,\"lat\":48.85833,\"tst\":1497349316,\"_type\":\"location\",\"tid\":\"JJ\"}"))); verifyPosition(decoder, request(HttpMethod.POST, "/", - buffer(text("{\"cog\":271,\"lon\":2.29513,\"acc\":5,\"vel\":61,\"vac\":21,\"lat\":48.85833,\"tst\":1497349316,\"alt\":167,\"_type\":\"location\",\"tid\":\"JJ\",\"t\":\"u\",\"batt\":67}")))); + buffer("{\"cog\":271,\"lon\":2.29513,\"acc\":5,\"vel\":61,\"vac\":21,\"lat\":48.85833,\"tst\":1497349316,\"alt\":167,\"_type\":\"location\",\"tid\":\"JJ\",\"t\":\"u\",\"batt\":67}"))); + verifyPosition(decoder, request(HttpMethod.POST, "/", + buffer("{\"lat\":48.85,\"lon\":2.295,\"_type\":\"location\",\"tid\":\"JJ\",\"tst\":1497476456}"))); } } diff --git a/test/org/traccar/reports/ReportUtilsTest.java b/test/org/traccar/reports/ReportUtilsTest.java index 2f785c538..2fe05e688 100644 --- a/test/org/traccar/reports/ReportUtilsTest.java +++ b/test/org/traccar/reports/ReportUtilsTest.java @@ -1,10 +1,48 @@ package org.traccar.reports; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Arrays; +import java.util.Collection; +import java.util.Date; +import java.util.TimeZone; + import org.junit.Assert; import org.junit.Test; +import org.traccar.BaseTest; import org.traccar.model.Position; +import org.traccar.reports.model.BaseReport; +import org.traccar.reports.model.StopReport; +import org.traccar.reports.model.TripReport; +import org.traccar.reports.model.TripsConfig; + +public class ReportUtilsTest extends BaseTest { + + private Date date(String time) throws ParseException { + DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); + dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); + return dateFormat.parse(time); + } -public class ReportUtilsTest { + private Position position(String time, double speed, double totalDistance) throws ParseException { + + Position position = new Position(); + + if (time != null) { + position.setTime(date(time)); + } + position.setValid(true); + position.setSpeed(speed); + position.set(Position.KEY_TOTAL_DISTANCE, totalDistance); + + return position; + } @Test public void testCalculateDistance() { @@ -28,4 +66,198 @@ public class ReportUtilsTest { Assert.assertEquals(ReportUtils.calculateFuel(startPosition, endPosition), "0.2"); } + @Test + public void testDetectTripsSimple() throws ParseException { + + Collection<Position> data = Arrays.asList( + position("2016-01-01 00:00:00.000", 0, 0), + position("2016-01-01 00:01:00.000", 0, 0), + position("2016-01-01 00:02:00.000", 10, 0), + position("2016-01-01 00:03:00.000", 10, 1000), + position("2016-01-01 00:04:00.000", 10, 2000), + position("2016-01-01 00:05:00.000", 0, 3000), + position("2016-01-01 00:06:00.000", 0, 3000), + position("2016-01-01 00:07:00.000", 0, 3000)); + + TripsConfig tripsConfig = new TripsConfig(500, 300000, 180000, false, 900000); + + Collection<? extends BaseReport> result = ReportUtils.detectTripsAndStops(tripsConfig, false, 0.01, data, true); + + assertNotNull(result); + assertFalse(result.isEmpty()); + + TripReport itemTrip = (TripReport) result.iterator().next(); + + assertEquals(date("2016-01-01 00:02:00.000"), itemTrip.getStartTime()); + assertEquals(date("2016-01-01 00:05:00.000"), itemTrip.getEndTime()); + assertEquals(180000, itemTrip.getDuration()); + assertEquals(10, itemTrip.getAverageSpeed(), 0.01); + assertEquals(10, itemTrip.getMaxSpeed(), 0.01); + assertEquals(3000, itemTrip.getDistance(), 0.01); + + result = ReportUtils.detectTripsAndStops(tripsConfig, false, 0.01, data, false); + + assertNotNull(result); + assertFalse(result.isEmpty()); + + StopReport itemStop = (StopReport) result.iterator().next(); + + assertEquals(date("2016-01-01 00:05:00.000"), itemStop.getStartTime()); + assertEquals(date("2016-01-01 00:07:00.000"), itemStop.getEndTime()); + assertEquals(120000, itemStop.getDuration()); + + } + + @Test + public void testDetectStopsOnly() throws ParseException { + + Collection<Position> data = Arrays.asList( + position("2016-01-01 00:00:00.000", 0, 0), + position("2016-01-01 00:01:00.000", 0, 0), + position("2016-01-01 00:02:00.000", 1, 0), + position("2016-01-01 00:03:00.000", 0, 0), + position("2016-01-01 00:04:00.000", 1, 0), + position("2016-01-01 00:05:00.000", 0, 0)); + + TripsConfig tripsConfig = new TripsConfig(500, 300000, 200000, false, 900000); + + Collection<? extends BaseReport> result = ReportUtils.detectTripsAndStops(tripsConfig, false, 0.01, data, false); + + assertNotNull(result); + assertFalse(result.isEmpty()); + + StopReport itemStop = (StopReport) result.iterator().next(); + + assertEquals(date("2016-01-01 00:00:00.000"), itemStop.getStartTime()); + assertEquals(date("2016-01-01 00:05:00.000"), itemStop.getEndTime()); + assertEquals(300000, itemStop.getDuration()); + + } + + @Test + public void testDetectStopsWithTripCut() throws ParseException { + + Collection<Position> data = Arrays.asList( + position("2016-01-01 00:00:00.000", 0, 0), + position("2016-01-01 00:01:00.000", 0, 0), + position("2016-01-01 00:02:00.000", 0, 0), + position("2016-01-01 00:03:00.000", 0, 0), + position("2016-01-01 00:04:00.000", 1, 0), + position("2016-01-01 00:05:00.000", 2, 0)); + + TripsConfig tripsConfig = new TripsConfig(500, 300000, 200000, false, 900000); + + Collection<? extends BaseReport> result = ReportUtils.detectTripsAndStops(tripsConfig, false, 0.01, data, false); + + assertNotNull(result); + assertFalse(result.isEmpty()); + + StopReport itemStop = (StopReport) result.iterator().next(); + + assertEquals(date("2016-01-01 00:00:00.000"), itemStop.getStartTime()); + assertEquals(date("2016-01-01 00:04:00.000"), itemStop.getEndTime()); + assertEquals(240000, itemStop.getDuration()); + + tripsConfig.setGreedyParking(true); + + result = ReportUtils.detectTripsAndStops(tripsConfig, false, 0.01, data, false); + + assertNotNull(result); + assertFalse(result.isEmpty()); + + itemStop = (StopReport) result.iterator().next(); + + assertEquals(date("2016-01-01 00:00:00.000"), itemStop.getStartTime()); + assertEquals(date("2016-01-01 00:05:00.000"), itemStop.getEndTime()); + assertEquals(300000, itemStop.getDuration()); + + } + + @Test + public void testDetectStopsStartedFromTrip() throws ParseException { + + Collection<Position> data = Arrays.asList( + position("2016-01-01 00:00:00.000", 2, 0), + position("2016-01-01 00:01:00.000", 1, 0), + position("2016-01-01 00:02:00.000", 0, 0), + position("2016-01-01 00:03:00.000", 0, 0), + position("2016-01-01 00:04:00.000", 0, 0), + position("2016-01-01 00:05:00.000", 0, 0)); + + TripsConfig tripsConfig = new TripsConfig(500, 300000, 200000, false, 900000); + + Collection<? extends BaseReport> result = ReportUtils.detectTripsAndStops(tripsConfig, false, 0.01, data, false); + + assertNotNull(result); + assertFalse(result.isEmpty()); + + StopReport itemStop = (StopReport) result.iterator().next(); + + assertEquals(date("2016-01-01 00:00:00.000"), itemStop.getStartTime()); + assertEquals(date("2016-01-01 00:05:00.000"), itemStop.getEndTime()); + assertEquals(300000, itemStop.getDuration()); + + } + + @Test + public void testDetectStopsMoving() throws ParseException { + + Collection<Position> data = Arrays.asList( + position("2016-01-01 00:00:00.000", 5, 0), + position("2016-01-01 00:01:00.000", 5, 0), + position("2016-01-01 00:02:00.000", 3, 0), + position("2016-01-01 00:03:00.000", 5, 0), + position("2016-01-01 00:04:00.000", 5, 0), + position("2016-01-01 00:05:00.000", 5, 0)); + + TripsConfig tripsConfig = new TripsConfig(500, 300000, 200000, false, 900000); + + Collection<? extends BaseReport> result = ReportUtils.detectTripsAndStops(tripsConfig, false, 0.01, data, false); + + assertNotNull(result); + assertTrue(result.isEmpty()); + + } + + @Test + public void testDetectTripAndStopByGap() throws ParseException { + + Collection<Position> data = Arrays.asList( + position("2016-01-01 00:00:00.000", 7, 100), + position("2016-01-01 00:01:00.000", 7, 300), + position("2016-01-01 00:02:00.000", 5, 500), + position("2016-01-01 00:03:00.000", 5, 600), + position("2016-01-01 00:04:00.000", 3, 700), + position("2016-01-01 00:23:00.000", 2, 700), + position("2016-01-01 00:24:00.000", 5, 800), + position("2016-01-01 00:25:00.000", 5, 900)); + + TripsConfig tripsConfig = new TripsConfig(500, 200000, 200000, false, 900000); + + Collection<? extends BaseReport> result = ReportUtils.detectTripsAndStops(tripsConfig, false, 0.01, data, true); + + assertNotNull(result); + assertFalse(result.isEmpty()); + + TripReport itemTrip = (TripReport) result.iterator().next(); + + assertEquals(date("2016-01-01 00:00:00.000"), itemTrip.getStartTime()); + assertEquals(date("2016-01-01 00:04:00.000"), itemTrip.getEndTime()); + assertEquals(240000, itemTrip.getDuration()); + assertEquals(6.75, itemTrip.getAverageSpeed(), 0.01); + assertEquals(7, itemTrip.getMaxSpeed(), 0.01); + assertEquals(600, itemTrip.getDistance(), 0.01); + + result = ReportUtils.detectTripsAndStops(tripsConfig, false, 0.01, data, false); + + assertNotNull(result); + assertFalse(result.isEmpty()); + + StopReport itemStop = (StopReport) result.iterator().next(); + + assertEquals(date("2016-01-01 00:04:00.000"), itemStop.getStartTime()); + assertEquals(date("2016-01-01 00:23:00.000"), itemStop.getEndTime()); + assertEquals(1140000, itemStop.getDuration()); + } + } diff --git a/test/org/traccar/reports/TripsTest.java b/test/org/traccar/reports/TripsTest.java deleted file mode 100644 index dea101cf2..000000000 --- a/test/org/traccar/reports/TripsTest.java +++ /dev/null @@ -1,70 +0,0 @@ -package org.traccar.reports; - -import org.junit.Test; -import org.traccar.BaseTest; -import org.traccar.model.Position; -import org.traccar.reports.model.TripReport; - -import java.text.DateFormat; -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.Arrays; -import java.util.Collection; -import java.util.Date; -import java.util.TimeZone; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; - -public class TripsTest extends BaseTest { - - private Date date(String time) throws ParseException { - DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); - dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); - return dateFormat.parse(time); - } - - private Position position(String time, double speed, double totalDistance) throws ParseException { - - Position position = new Position(); - - if (time != null) { - position.setTime(date(time)); - } - position.setValid(true); - position.setSpeed(speed); - position.set(Position.KEY_TOTAL_DISTANCE, totalDistance); - - return position; - } - - @Test - public void testDetectTripsSimple() throws ParseException { - - Collection<Position> data = Arrays.asList( - position("2016-01-01 00:00:00.000", 0, 0), - position("2016-01-01 00:01:00.000", 0, 0), - position("2016-01-01 00:02:00.000", 10, 0), - position("2016-01-01 00:03:00.000", 10, 1000), - position("2016-01-01 00:04:00.000", 10, 2000), - position("2016-01-01 00:05:00.000", 0, 3000), - position("2016-01-01 00:06:00.000", 0, 3000)); - - Collection<TripReport> result = Trips.detectTrips(0.01, 500, 300000, 300000, false, false, data); - - assertNotNull(result); - assertFalse(result.isEmpty()); - - TripReport item = result.iterator().next(); - - assertEquals(date("2016-01-01 00:02:00.000"), item.getStartTime()); - assertEquals(date("2016-01-01 00:05:00.000"), item.getEndTime()); - assertEquals(180000, item.getDuration()); - assertEquals(10, item.getAverageSpeed(), 0.01); - assertEquals(10, item.getMaxSpeed(), 0.01); - assertEquals(3000, item.getDistance(), 0.01); - - } - -} |