From e9a93ff35c1f923245ffdde78e4023b434173d64 Mon Sep 17 00:00:00 2001 From: Anton Tananaev Date: Tue, 23 May 2023 16:17:34 -0700 Subject: Fast summary report --- src/main/java/org/traccar/config/Keys.java | 9 ++ .../org/traccar/reports/SummaryReportProvider.java | 120 ++++++++++++--------- 2 files changed, 77 insertions(+), 52 deletions(-) (limited to 'src') diff --git a/src/main/java/org/traccar/config/Keys.java b/src/main/java/org/traccar/config/Keys.java index 4ebdd49f1..8246fdeac 100644 --- a/src/main/java/org/traccar/config/Keys.java +++ b/src/main/java/org/traccar/config/Keys.java @@ -1170,6 +1170,15 @@ public final class Keys { "report.periodLimit", List.of(KeyType.CONFIG)); + /** + * Time threshold for fast reports. Fast reports are more efficient, but less accurate and missing some information. + * The value is in seconds. One day by default. + */ + public static final ConfigKey REPORT_FAST_THRESHOLD = new LongConfigKey( + "report.fastThreshold", + List.of(KeyType.CONFIG), + 86400L); + /** * Trips less than minimal duration and minimal distance are ignored. 300 seconds and 500 meters are default. */ diff --git a/src/main/java/org/traccar/reports/SummaryReportProvider.java b/src/main/java/org/traccar/reports/SummaryReportProvider.java index f7d9f0325..2226263fa 100644 --- a/src/main/java/org/traccar/reports/SummaryReportProvider.java +++ b/src/main/java/org/traccar/reports/SummaryReportProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 - 2022 Anton Tananaev (anton@traccar.org) + * Copyright 2016 - 2023 Anton Tananaev (anton@traccar.org) * Copyright 2016 Andrey Kunitsyn (andrey@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -30,6 +30,10 @@ import org.traccar.reports.common.ReportUtils; import org.traccar.reports.model.SummaryReportItem; import org.traccar.storage.Storage; import org.traccar.storage.StorageException; +import org.traccar.storage.query.Columns; +import org.traccar.storage.query.Condition; +import org.traccar.storage.query.Order; +import org.traccar.storage.query.Request; import javax.inject.Inject; import java.io.File; @@ -38,10 +42,13 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.file.Paths; +import java.time.Duration; +import java.time.ZonedDateTime; +import java.time.temporal.ChronoUnit; import java.util.ArrayList; -import java.util.Calendar; import java.util.Collection; import java.util.Date; +import java.util.List; public class SummaryReportProvider { @@ -59,96 +66,105 @@ public class SummaryReportProvider { this.storage = storage; } - private SummaryReportItem calculateSummaryResult(Device device, Collection positions) { + private Position getEdgePosition(long deviceId, Date from, Date to, boolean end) throws StorageException { + return storage.getObject(Position.class, new Request( + new Columns.All(), + new Condition.And( + new Condition.Equals("deviceId", deviceId), + new Condition.Between("fixTime", "from", from, "to", to)), + new Order("fixTime", end, 1))); + } + + private Collection calculateDeviceResult( + Device device, Date from, Date to, boolean fast) throws StorageException { + SummaryReportItem result = new SummaryReportItem(); result.setDeviceId(device.getId()); result.setDeviceName(device.getName()); - if (positions != null && !positions.isEmpty()) { - Position firstPosition = null; - Position previousPosition = null; + + Position first = null; + Position last = null; + if (fast) { + first = getEdgePosition(device.getId(), from, to, false); + last = getEdgePosition(device.getId(), from, to, true); + } else { + var positions = PositionUtil.getPositions(storage, device.getId(), from, to); for (Position position : positions) { - if (firstPosition == null) { - firstPosition = position; + if (first == null) { + first = position; } - previousPosition = position; if (position.getSpeed() > result.getMaxSpeed()) { result.setMaxSpeed(position.getSpeed()); } + last = position; } + } + + if (first != null && last != null) { boolean ignoreOdometer = config.getBoolean(Keys.REPORT_IGNORE_ODOMETER); - result.setDistance(PositionUtil.calculateDistance(firstPosition, previousPosition, !ignoreOdometer)); - result.setSpentFuel(reportUtils.calculateFuel(firstPosition, previousPosition)); + result.setDistance(PositionUtil.calculateDistance(first, last, !ignoreOdometer)); + result.setSpentFuel(reportUtils.calculateFuel(first, last)); long durationMilliseconds; - if (firstPosition.hasAttribute(Position.KEY_HOURS) && previousPosition.hasAttribute(Position.KEY_HOURS)) { - durationMilliseconds = - previousPosition.getLong(Position.KEY_HOURS) - firstPosition.getLong(Position.KEY_HOURS); + if (first.hasAttribute(Position.KEY_HOURS) && last.hasAttribute(Position.KEY_HOURS)) { + durationMilliseconds = last.getLong(Position.KEY_HOURS) - first.getLong(Position.KEY_HOURS); result.setEngineHours(durationMilliseconds); } else { - durationMilliseconds = - previousPosition.getFixTime().getTime() - firstPosition.getFixTime().getTime(); + durationMilliseconds = last.getFixTime().getTime() - first.getFixTime().getTime(); } if (durationMilliseconds > 0) { - result.setAverageSpeed( - UnitsConverter.knotsFromMps(result.getDistance() * 1000 / durationMilliseconds)); + result.setAverageSpeed(UnitsConverter.knotsFromMps(result.getDistance() * 1000 / durationMilliseconds)); } if (!ignoreOdometer - && firstPosition.getDouble(Position.KEY_ODOMETER) != 0 - && previousPosition.getDouble(Position.KEY_ODOMETER) != 0) { - result.setStartOdometer(firstPosition.getDouble(Position.KEY_ODOMETER)); - result.setEndOdometer(previousPosition.getDouble(Position.KEY_ODOMETER)); + && first.getDouble(Position.KEY_ODOMETER) != 0 && last.getDouble(Position.KEY_ODOMETER) != 0) { + result.setStartOdometer(first.getDouble(Position.KEY_ODOMETER)); + result.setEndOdometer(last.getDouble(Position.KEY_ODOMETER)); } else { - result.setStartOdometer(firstPosition.getDouble(Position.KEY_TOTAL_DISTANCE)); - result.setEndOdometer(previousPosition.getDouble(Position.KEY_TOTAL_DISTANCE)); + result.setStartOdometer(first.getDouble(Position.KEY_TOTAL_DISTANCE)); + result.setEndOdometer(last.getDouble(Position.KEY_TOTAL_DISTANCE)); } - result.setStartTime(firstPosition.getFixTime()); - result.setEndTime(previousPosition.getFixTime()); + result.setStartTime(first.getFixTime()); + result.setEndTime(last.getFixTime()); + return List.of(result); } - return result; - } - private int getDay(long userId, Date date) throws StorageException { - Calendar calendar = Calendar.getInstance(UserUtil.getTimezone( - permissionsService.getServer(), permissionsService.getUser(userId))); - calendar.setTime(date); - return calendar.get(Calendar.DAY_OF_MONTH); + return List.of(); } - private Collection calculateSummaryResults( - long userId, Device device, Date from, Date to, boolean daily) throws StorageException { + private Collection calculateDeviceResults( + Device device, ZonedDateTime from, ZonedDateTime to, boolean daily) throws StorageException { - var positions = PositionUtil.getPositions(storage, device.getId(), from, to); + boolean fast = Duration.between(from, to).toSeconds() > config.getLong(Keys.REPORT_FAST_THRESHOLD); var results = new ArrayList(); - if (daily && !positions.isEmpty()) { - int startIndex = 0; - int startDay = getDay(userId, positions.iterator().next().getFixTime()); - for (int i = 0; i < positions.size(); i++) { - int currentDay = getDay(userId, positions.get(i).getFixTime()); - if (currentDay != startDay) { - results.add(calculateSummaryResult(device, positions.subList(startIndex, i))); - startIndex = i; - startDay = currentDay; - } + if (daily) { + while (from.truncatedTo(ChronoUnit.DAYS).isBefore(to.truncatedTo(ChronoUnit.DAYS))) { + ZonedDateTime fromDay = from.truncatedTo(ChronoUnit.DAYS); + ZonedDateTime nextDay = fromDay.plus(1, ChronoUnit.DAYS); + results.addAll(calculateDeviceResult( + device, Date.from(from.toInstant()), Date.from(nextDay.toInstant()), fast)); + from = nextDay; } - results.add(calculateSummaryResult(device, positions.subList(startIndex, positions.size()))); + results.addAll(calculateDeviceResult(device, Date.from(from.toInstant()), Date.from(to.toInstant()), fast)); } else { - results.add(calculateSummaryResult(device, positions)); + results.addAll(calculateDeviceResult(device, Date.from(from.toInstant()), Date.from(to.toInstant()), fast)); } - return results; } public Collection getObjects( - long userId, Collection deviceIds, - Collection groupIds, Date from, Date to, boolean daily) throws StorageException { + long userId, Collection deviceIds, Collection groupIds, + Date from, Date to, boolean daily) throws StorageException { reportUtils.checkPeriodLimit(from, to); + var tz = UserUtil.getTimezone(permissionsService.getServer(), permissionsService.getUser(userId)).toZoneId(); + ArrayList result = new ArrayList<>(); for (Device device: DeviceUtil.getAccessibleDevices(storage, userId, deviceIds, groupIds)) { - Collection deviceResults = calculateSummaryResults(userId, device, from, to, daily); + var deviceResults = calculateDeviceResults( + device, from.toInstant().atZone(tz), to.toInstant().atZone(tz), daily); for (SummaryReportItem summaryReport : deviceResults) { if (summaryReport.getStartTime() != null && summaryReport.getEndTime() != null) { result.add(summaryReport); -- cgit v1.2.3