From b45b64ce97113c68ef6118665c4a8b0106249264 Mon Sep 17 00:00:00 2001 From: Anton Tananaev Date: Sat, 4 Jun 2022 10:52:23 -0700 Subject: Move common report classes --- src/main/java/org/traccar/Context.java | 2 +- src/main/java/org/traccar/MainModule.java | 2 +- .../java/org/traccar/handler/MotionHandler.java | 2 +- .../traccar/handler/events/MotionEventHandler.java | 4 +- .../notification/NotificationFormatter.java | 2 +- .../notification/TextTemplateFormatter.java | 2 +- src/main/java/org/traccar/reports/Events.java | 1 + src/main/java/org/traccar/reports/ReportUtils.java | 388 --------------------- src/main/java/org/traccar/reports/Route.java | 1 + src/main/java/org/traccar/reports/Stops.java | 1 + src/main/java/org/traccar/reports/Summary.java | 1 + src/main/java/org/traccar/reports/Trips.java | 1 + .../org/traccar/reports/common/ReportUtils.java | 387 ++++++++++++++++++++ .../org/traccar/reports/common/TripsConfig.java | 74 ++++ .../org/traccar/reports/model/TripsConfig.java | 74 ---- 15 files changed, 473 insertions(+), 469 deletions(-) delete mode 100644 src/main/java/org/traccar/reports/ReportUtils.java create mode 100644 src/main/java/org/traccar/reports/common/ReportUtils.java create mode 100644 src/main/java/org/traccar/reports/common/TripsConfig.java delete mode 100644 src/main/java/org/traccar/reports/model/TripsConfig.java (limited to 'src/main') diff --git a/src/main/java/org/traccar/Context.java b/src/main/java/org/traccar/Context.java index a833d0c52..dde98f505 100644 --- a/src/main/java/org/traccar/Context.java +++ b/src/main/java/org/traccar/Context.java @@ -48,7 +48,7 @@ import org.traccar.model.Notification; import org.traccar.model.User; import org.traccar.notification.EventForwarder; import org.traccar.notification.NotificatorManager; -import org.traccar.reports.model.TripsConfig; +import org.traccar.reports.common.TripsConfig; import org.traccar.session.ConnectionManager; import org.traccar.sms.HttpSmsClient; import org.traccar.sms.SmsManager; diff --git a/src/main/java/org/traccar/MainModule.java b/src/main/java/org/traccar/MainModule.java index 219a8fc11..fb70a51c6 100644 --- a/src/main/java/org/traccar/MainModule.java +++ b/src/main/java/org/traccar/MainModule.java @@ -58,7 +58,7 @@ import org.traccar.geolocation.UnwiredGeolocationProvider; import org.traccar.handler.GeocoderHandler; import org.traccar.handler.GeolocationHandler; import org.traccar.handler.SpeedLimitHandler; -import org.traccar.reports.model.TripsConfig; +import org.traccar.reports.common.TripsConfig; import org.traccar.sms.SmsManager; import org.traccar.speedlimit.OverpassSpeedLimitProvider; import org.traccar.speedlimit.SpeedLimitProvider; diff --git a/src/main/java/org/traccar/handler/MotionHandler.java b/src/main/java/org/traccar/handler/MotionHandler.java index 864eb455d..7b2c04ecf 100644 --- a/src/main/java/org/traccar/handler/MotionHandler.java +++ b/src/main/java/org/traccar/handler/MotionHandler.java @@ -19,7 +19,7 @@ package org.traccar.handler; import io.netty.channel.ChannelHandler; import org.traccar.BaseDataHandler; import org.traccar.model.Position; -import org.traccar.reports.model.TripsConfig; +import org.traccar.reports.common.TripsConfig; import javax.inject.Inject; diff --git a/src/main/java/org/traccar/handler/events/MotionEventHandler.java b/src/main/java/org/traccar/handler/events/MotionEventHandler.java index e27faf9ce..57d2bc1c5 100644 --- a/src/main/java/org/traccar/handler/events/MotionEventHandler.java +++ b/src/main/java/org/traccar/handler/events/MotionEventHandler.java @@ -26,8 +26,8 @@ import org.traccar.model.Device; import org.traccar.session.DeviceState; import org.traccar.model.Event; import org.traccar.model.Position; -import org.traccar.reports.ReportUtils; -import org.traccar.reports.model.TripsConfig; +import org.traccar.reports.common.ReportUtils; +import org.traccar.reports.common.TripsConfig; import javax.inject.Inject; diff --git a/src/main/java/org/traccar/notification/NotificationFormatter.java b/src/main/java/org/traccar/notification/NotificationFormatter.java index 107426e06..30a862372 100644 --- a/src/main/java/org/traccar/notification/NotificationFormatter.java +++ b/src/main/java/org/traccar/notification/NotificationFormatter.java @@ -24,7 +24,7 @@ import org.traccar.model.Geofence; import org.traccar.model.Maintenance; import org.traccar.model.Position; import org.traccar.model.User; -import org.traccar.reports.ReportUtils; +import org.traccar.reports.common.ReportUtils; import org.traccar.session.cache.CacheManager; public final class NotificationFormatter { diff --git a/src/main/java/org/traccar/notification/TextTemplateFormatter.java b/src/main/java/org/traccar/notification/TextTemplateFormatter.java index b7058c824..469de2d4a 100644 --- a/src/main/java/org/traccar/notification/TextTemplateFormatter.java +++ b/src/main/java/org/traccar/notification/TextTemplateFormatter.java @@ -24,7 +24,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.traccar.Context; import org.traccar.model.User; -import org.traccar.reports.ReportUtils; +import org.traccar.reports.common.ReportUtils; import java.io.StringWriter; import java.nio.charset.StandardCharsets; diff --git a/src/main/java/org/traccar/reports/Events.java b/src/main/java/org/traccar/reports/Events.java index 818142b16..c5db305ed 100644 --- a/src/main/java/org/traccar/reports/Events.java +++ b/src/main/java/org/traccar/reports/Events.java @@ -33,6 +33,7 @@ import org.traccar.model.Event; import org.traccar.model.Geofence; import org.traccar.model.Group; import org.traccar.model.Maintenance; +import org.traccar.reports.common.ReportUtils; import org.traccar.reports.model.DeviceReport; import org.traccar.storage.Storage; import org.traccar.storage.StorageException; diff --git a/src/main/java/org/traccar/reports/ReportUtils.java b/src/main/java/org/traccar/reports/ReportUtils.java deleted file mode 100644 index 98a80a23e..000000000 --- a/src/main/java/org/traccar/reports/ReportUtils.java +++ /dev/null @@ -1,388 +0,0 @@ -/* - * Copyright 2016 - 2022 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. - * 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 org.apache.velocity.tools.generic.DateTool; -import org.apache.velocity.tools.generic.NumberTool; -import org.jxls.area.Area; -import org.jxls.builder.xls.XlsCommentAreaBuilder; -import org.jxls.common.CellRef; -import org.jxls.formula.StandardFormulaProcessor; -import org.jxls.transform.Transformer; -import org.jxls.transform.poi.PoiTransformer; -import org.jxls.util.TransformerFactory; -import org.traccar.Context; -import org.traccar.config.Keys; -import org.traccar.database.DeviceManager; -import org.traccar.database.IdentityManager; -import org.traccar.handler.events.MotionEventHandler; -import org.traccar.helper.UnitsConverter; -import org.traccar.model.BaseModel; -import org.traccar.model.User; -import org.traccar.session.DeviceState; -import org.traccar.model.Driver; -import org.traccar.model.Event; -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 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.Request; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.math.BigDecimal; -import java.math.RoundingMode; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Date; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.TimeZone; - -public final class ReportUtils { - - private ReportUtils() { - } - - public static T getObject( - Storage storage, long userId, Class clazz, long objectId) throws StorageException, SecurityException { - return storage.getObject(clazz, new Request( - new Columns.Include("id"), - new Condition.And( - new Condition.Equals("id", "id", objectId), - new Condition.Permission(User.class, userId, clazz)))); - } - - public static void checkPeriodLimit(Date from, Date to) { - long limit = Context.getConfig().getLong(Keys.REPORT_PERIOD_LIMIT) * 1000; - if (limit > 0 && to.getTime() - from.getTime() > limit) { - throw new IllegalArgumentException("Time period exceeds the limit"); - } - } - - public static String getDistanceUnit(long userId) { - return (String) Context.getPermissionsManager().lookupAttribute(userId, "distanceUnit", "km"); - } - - public static String getSpeedUnit(long userId) { - return (String) Context.getPermissionsManager().lookupAttribute(userId, "speedUnit", "kn"); - } - - public static String getVolumeUnit(long userId) { - return (String) Context.getPermissionsManager().lookupAttribute(userId, "volumeUnit", "ltr"); - } - - public static TimeZone getTimezone(long userId) { - String timezone = (String) Context.getPermissionsManager().lookupAttribute(userId, "timezone", null); - return timezone != null ? TimeZone.getTimeZone(timezone) : TimeZone.getDefault(); - } - - public static Collection getDeviceList(Collection deviceIds, Collection groupIds) { - Collection result = new LinkedHashSet<>(deviceIds); - for (long groupId : groupIds) { - result.addAll(Context.getPermissionsManager().getGroupDevices(groupId)); - } - return result; - } - - public static double calculateDistance(Position firstPosition, Position lastPosition) { - return calculateDistance(firstPosition, lastPosition, true); - } - - public static double calculateDistance(Position firstPosition, Position lastPosition, boolean useOdometer) { - double distance = 0.0; - double firstOdometer = firstPosition.getDouble(Position.KEY_ODOMETER); - double lastOdometer = lastPosition.getDouble(Position.KEY_ODOMETER); - - if (useOdometer && firstOdometer != 0.0 && lastOdometer != 0.0) { - distance = lastOdometer - firstOdometer; - } else if (firstPosition.getAttributes().containsKey(Position.KEY_TOTAL_DISTANCE) - && lastPosition.getAttributes().containsKey(Position.KEY_TOTAL_DISTANCE)) { - distance = lastPosition.getDouble(Position.KEY_TOTAL_DISTANCE) - - firstPosition.getDouble(Position.KEY_TOTAL_DISTANCE); - } - return distance; - } - - public static double calculateFuel(Position firstPosition, Position lastPosition) { - - if (firstPosition.getAttributes().get(Position.KEY_FUEL_LEVEL) != null - && lastPosition.getAttributes().get(Position.KEY_FUEL_LEVEL) != null) { - - BigDecimal value = BigDecimal.valueOf(firstPosition.getDouble(Position.KEY_FUEL_LEVEL) - - lastPosition.getDouble(Position.KEY_FUEL_LEVEL)); - return value.setScale(1, RoundingMode.HALF_EVEN).doubleValue(); - } - return 0; - } - - public static String findDriver(Position firstPosition, Position lastPosition) { - if (firstPosition.getAttributes().containsKey(Position.KEY_DRIVER_UNIQUE_ID)) { - return firstPosition.getString(Position.KEY_DRIVER_UNIQUE_ID); - } else if (lastPosition.getAttributes().containsKey(Position.KEY_DRIVER_UNIQUE_ID)) { - return lastPosition.getString(Position.KEY_DRIVER_UNIQUE_ID); - } - return null; - } - - public static String findDriverName(String driverUniqueId) { - if (driverUniqueId != null && Context.getDriversManager() != null) { - Driver driver = Context.getDriversManager().getDriverByUniqueId(driverUniqueId); - if (driver != null) { - return driver.getName(); - } - } - return null; - } - - public static org.jxls.common.Context initializeContext(long userId) { - org.jxls.common.Context jxlsContext = PoiTransformer.createInitialContext(); - jxlsContext.putVar("distanceUnit", getDistanceUnit(userId)); - jxlsContext.putVar("speedUnit", getSpeedUnit(userId)); - jxlsContext.putVar("volumeUnit", getVolumeUnit(userId)); - jxlsContext.putVar("webUrl", Context.getVelocityEngine().getProperty("web.url")); - jxlsContext.putVar("dateTool", new DateTool()); - jxlsContext.putVar("numberTool", new NumberTool()); - jxlsContext.putVar("timezone", getTimezone(userId)); - jxlsContext.putVar("locale", Locale.getDefault()); - jxlsContext.putVar("bracketsRegex", "[\\{\\}\"]"); - return jxlsContext; - } - - public static void processTemplateWithSheets( - InputStream templateStream, OutputStream targetStream, - org.jxls.common.Context jxlsContext) throws IOException { - - Transformer transformer = TransformerFactory.createTransformer(templateStream, targetStream); - List xlsAreas = new XlsCommentAreaBuilder(transformer).build(); - for (Area xlsArea : xlsAreas) { - xlsArea.applyAt(new CellRef(xlsArea.getStartCellRef().getCellName()), jxlsContext); - xlsArea.setFormulaProcessor(new StandardFormulaProcessor()); - xlsArea.processFormulas(); - } - transformer.deleteSheet(xlsAreas.get(0).getStartCellRef().getSheetName()); - transformer.write(); - } - - private static TripReport calculateTrip( - IdentityManager identityManager, ArrayList positions, - int startIndex, int endIndex, boolean ignoreOdometer) { - - Position startTrip = positions.get(startIndex); - Position endTrip = positions.get(endIndex); - - double speedMax = 0; - for (int i = startIndex; i <= endIndex; i++) { - double speed = positions.get(i).getSpeed(); - 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(identityManager.getById(deviceId).getName()); - - trip.setStartPositionId(startTrip.getId()); - trip.setStartLat(startTrip.getLatitude()); - trip.setStartLon(startTrip.getLongitude()); - trip.setStartTime(startTrip.getFixTime()); - String startAddress = startTrip.getAddress(); - if (startAddress == null && Context.getGeocoder() != null - && Context.getConfig().getBoolean(Keys.GEOCODER_ON_REQUEST)) { - startAddress = Context.getGeocoder().getAddress(startTrip.getLatitude(), startTrip.getLongitude(), null); - } - trip.setStartAddress(startAddress); - - trip.setEndPositionId(endTrip.getId()); - trip.setEndLat(endTrip.getLatitude()); - trip.setEndLon(endTrip.getLongitude()); - trip.setEndTime(endTrip.getFixTime()); - String endAddress = endTrip.getAddress(); - if (endAddress == null && Context.getGeocoder() != null - && Context.getConfig().getBoolean(Keys.GEOCODER_ON_REQUEST)) { - endAddress = Context.getGeocoder().getAddress(endTrip.getLatitude(), endTrip.getLongitude(), null); - } - trip.setEndAddress(endAddress); - - trip.setDistance(calculateDistance(startTrip, endTrip, !ignoreOdometer)); - trip.setDuration(tripDuration); - if (tripDuration > 0) { - trip.setAverageSpeed(UnitsConverter.knotsFromMps(trip.getDistance() * 1000 / tripDuration)); - } - trip.setMaxSpeed(speedMax); - trip.setSpentFuel(calculateFuel(startTrip, endTrip)); - - trip.setDriverUniqueId(findDriver(startTrip, endTrip)); - trip.setDriverName(findDriverName(trip.getDriverUniqueId())); - - if (!ignoreOdometer - && startTrip.getDouble(Position.KEY_ODOMETER) != 0 - && endTrip.getDouble(Position.KEY_ODOMETER) != 0) { - trip.setStartOdometer(startTrip.getDouble(Position.KEY_ODOMETER)); - trip.setEndOdometer(endTrip.getDouble(Position.KEY_ODOMETER)); - } else { - trip.setStartOdometer(startTrip.getDouble(Position.KEY_TOTAL_DISTANCE)); - trip.setEndOdometer(endTrip.getDouble(Position.KEY_TOTAL_DISTANCE)); - } - - return trip; - } - - private static StopReport calculateStop( - IdentityManager identityManager, ArrayList positions, - int startIndex, int endIndex, boolean ignoreOdometer) { - - Position startStop = positions.get(startIndex); - Position endStop = positions.get(endIndex); - - StopReport stop = new StopReport(); - - long deviceId = startStop.getDeviceId(); - stop.setDeviceId(deviceId); - stop.setDeviceName(identityManager.getById(deviceId).getName()); - - stop.setPositionId(startStop.getId()); - stop.setLatitude(startStop.getLatitude()); - stop.setLongitude(startStop.getLongitude()); - stop.setStartTime(startStop.getFixTime()); - String address = startStop.getAddress(); - if (address == null && Context.getGeocoder() != null - && Context.getConfig().getBoolean(Keys.GEOCODER_ON_REQUEST)) { - address = Context.getGeocoder().getAddress(stop.getLatitude(), stop.getLongitude(), null); - } - stop.setAddress(address); - - stop.setEndTime(endStop.getFixTime()); - - long stopDuration = endStop.getFixTime().getTime() - startStop.getFixTime().getTime(); - stop.setDuration(stopDuration); - stop.setSpentFuel(calculateFuel(startStop, endStop)); - - if (startStop.getAttributes().containsKey(Position.KEY_HOURS) - && endStop.getAttributes().containsKey(Position.KEY_HOURS)) { - stop.setEngineHours(endStop.getLong(Position.KEY_HOURS) - startStop.getLong(Position.KEY_HOURS)); - } - - if (!ignoreOdometer - && startStop.getDouble(Position.KEY_ODOMETER) != 0 - && endStop.getDouble(Position.KEY_ODOMETER) != 0) { - stop.setStartOdometer(startStop.getDouble(Position.KEY_ODOMETER)); - stop.setEndOdometer(endStop.getDouble(Position.KEY_ODOMETER)); - } else { - stop.setStartOdometer(startStop.getDouble(Position.KEY_TOTAL_DISTANCE)); - stop.setEndOdometer(endStop.getDouble(Position.KEY_TOTAL_DISTANCE)); - } - - return stop; - - } - - private static T calculateTripOrStop( - IdentityManager identityManager, ArrayList positions, - int startIndex, int endIndex, boolean ignoreOdometer, Class reportClass) { - - if (reportClass.equals(TripReport.class)) { - return (T) calculateTrip(identityManager, positions, startIndex, endIndex, ignoreOdometer); - } else { - return (T) calculateStop(identityManager, positions, startIndex, endIndex, ignoreOdometer); - } - } - - private static boolean isMoving(ArrayList positions, int index, TripsConfig tripsConfig) { - if (tripsConfig.getMinimalNoDataDuration() > 0) { - boolean beforeGap = index < positions.size() - 1 - && positions.get(index + 1).getFixTime().getTime() - positions.get(index).getFixTime().getTime() - >= tripsConfig.getMinimalNoDataDuration(); - boolean afterGap = index > 0 - && positions.get(index).getFixTime().getTime() - positions.get(index - 1).getFixTime().getTime() - >= tripsConfig.getMinimalNoDataDuration(); - if (beforeGap || afterGap) { - return false; - } - } - if (positions.get(index).getAttributes().containsKey(Position.KEY_MOTION) - && positions.get(index).getAttributes().get(Position.KEY_MOTION) instanceof Boolean) { - return positions.get(index).getBoolean(Position.KEY_MOTION); - } else { - return positions.get(index).getSpeed() > tripsConfig.getSpeedThreshold(); - } - } - - public static Collection detectTripsAndStops( - IdentityManager identityManager, DeviceManager deviceManager, - Collection positionCollection, - TripsConfig tripsConfig, boolean ignoreOdometer, Class reportClass) { - - Collection result = new ArrayList<>(); - - ArrayList positions = new ArrayList<>(positionCollection); - if (!positions.isEmpty()) { - boolean trips = reportClass.equals(TripReport.class); - MotionEventHandler motionHandler = new MotionEventHandler(identityManager, deviceManager, tripsConfig); - DeviceState deviceState = new DeviceState(); - deviceState.setMotionState(isMoving(positions, 0, tripsConfig)); - int startEventIndex = trips == deviceState.getMotionState() ? 0 : -1; - int startNoEventIndex = -1; - for (int i = 0; i < positions.size(); i++) { - Map event = motionHandler.updateMotionState(deviceState, positions.get(i), - isMoving(positions, i, tripsConfig)); - if (startEventIndex == -1 - && (trips != deviceState.getMotionState() && deviceState.getMotionPosition() != null - || trips == deviceState.getMotionState() && event != null)) { - startEventIndex = i; - startNoEventIndex = -1; - } else if (trips != deviceState.getMotionState() && startEventIndex != -1 - && deviceState.getMotionPosition() == null && event == null) { - startEventIndex = -1; - } - if (startNoEventIndex == -1 - && (trips == deviceState.getMotionState() && deviceState.getMotionPosition() != null - || trips != deviceState.getMotionState() && event != null)) { - startNoEventIndex = i; - } else if (startNoEventIndex != -1 && deviceState.getMotionPosition() == null && event == null) { - startNoEventIndex = -1; - } - if (startEventIndex != -1 && startNoEventIndex != -1 && event != null - && trips != deviceState.getMotionState()) { - result.add(calculateTripOrStop(identityManager, positions, - startEventIndex, startNoEventIndex, ignoreOdometer, reportClass)); - startEventIndex = -1; - } - } - if (startEventIndex != -1 && (startNoEventIndex != -1 || !trips)) { - result.add(calculateTripOrStop(identityManager, positions, - startEventIndex, startNoEventIndex != -1 ? startNoEventIndex : positions.size() - 1, - ignoreOdometer, reportClass)); - } - } - - return result; - } - -} diff --git a/src/main/java/org/traccar/reports/Route.java b/src/main/java/org/traccar/reports/Route.java index 4a5edd295..d7745157a 100644 --- a/src/main/java/org/traccar/reports/Route.java +++ b/src/main/java/org/traccar/reports/Route.java @@ -29,6 +29,7 @@ import org.traccar.Context; import org.traccar.model.Device; import org.traccar.model.Group; import org.traccar.model.Position; +import org.traccar.reports.common.ReportUtils; import org.traccar.reports.model.DeviceReport; import org.traccar.storage.StorageException; diff --git a/src/main/java/org/traccar/reports/Stops.java b/src/main/java/org/traccar/reports/Stops.java index 36a4a7549..82eb62f66 100644 --- a/src/main/java/org/traccar/reports/Stops.java +++ b/src/main/java/org/traccar/reports/Stops.java @@ -32,6 +32,7 @@ import org.traccar.database.DeviceManager; import org.traccar.database.IdentityManager; import org.traccar.model.Device; import org.traccar.model.Group; +import org.traccar.reports.common.ReportUtils; import org.traccar.reports.model.DeviceReport; import org.traccar.reports.model.StopReport; import org.traccar.storage.StorageException; diff --git a/src/main/java/org/traccar/reports/Summary.java b/src/main/java/org/traccar/reports/Summary.java index f00b1ca57..20d80a9f5 100644 --- a/src/main/java/org/traccar/reports/Summary.java +++ b/src/main/java/org/traccar/reports/Summary.java @@ -29,6 +29,7 @@ import org.jxls.util.JxlsHelper; import org.traccar.Context; import org.traccar.helper.UnitsConverter; import org.traccar.model.Position; +import org.traccar.reports.common.ReportUtils; import org.traccar.reports.model.SummaryReport; import org.traccar.storage.StorageException; diff --git a/src/main/java/org/traccar/reports/Trips.java b/src/main/java/org/traccar/reports/Trips.java index 1461b869e..58131debb 100644 --- a/src/main/java/org/traccar/reports/Trips.java +++ b/src/main/java/org/traccar/reports/Trips.java @@ -31,6 +31,7 @@ import org.traccar.database.DeviceManager; import org.traccar.database.IdentityManager; import org.traccar.model.Device; import org.traccar.model.Group; +import org.traccar.reports.common.ReportUtils; import org.traccar.reports.model.DeviceReport; import org.traccar.reports.model.TripReport; import org.traccar.storage.StorageException; diff --git a/src/main/java/org/traccar/reports/common/ReportUtils.java b/src/main/java/org/traccar/reports/common/ReportUtils.java new file mode 100644 index 000000000..27972b453 --- /dev/null +++ b/src/main/java/org/traccar/reports/common/ReportUtils.java @@ -0,0 +1,387 @@ +/* + * Copyright 2016 - 2022 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. + * 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.common; + +import org.apache.velocity.tools.generic.DateTool; +import org.apache.velocity.tools.generic.NumberTool; +import org.jxls.area.Area; +import org.jxls.builder.xls.XlsCommentAreaBuilder; +import org.jxls.common.CellRef; +import org.jxls.formula.StandardFormulaProcessor; +import org.jxls.transform.Transformer; +import org.jxls.transform.poi.PoiTransformer; +import org.jxls.util.TransformerFactory; +import org.traccar.Context; +import org.traccar.config.Keys; +import org.traccar.database.DeviceManager; +import org.traccar.database.IdentityManager; +import org.traccar.handler.events.MotionEventHandler; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.BaseModel; +import org.traccar.model.User; +import org.traccar.session.DeviceState; +import org.traccar.model.Driver; +import org.traccar.model.Event; +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.storage.Storage; +import org.traccar.storage.StorageException; +import org.traccar.storage.query.Columns; +import org.traccar.storage.query.Condition; +import org.traccar.storage.query.Request; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.TimeZone; + +public final class ReportUtils { + + private ReportUtils() { + } + + public static T getObject( + Storage storage, long userId, Class clazz, long objectId) throws StorageException, SecurityException { + return storage.getObject(clazz, new Request( + new Columns.Include("id"), + new Condition.And( + new Condition.Equals("id", "id", objectId), + new Condition.Permission(User.class, userId, clazz)))); + } + + public static void checkPeriodLimit(Date from, Date to) { + long limit = Context.getConfig().getLong(Keys.REPORT_PERIOD_LIMIT) * 1000; + if (limit > 0 && to.getTime() - from.getTime() > limit) { + throw new IllegalArgumentException("Time period exceeds the limit"); + } + } + + public static String getDistanceUnit(long userId) { + return (String) Context.getPermissionsManager().lookupAttribute(userId, "distanceUnit", "km"); + } + + public static String getSpeedUnit(long userId) { + return (String) Context.getPermissionsManager().lookupAttribute(userId, "speedUnit", "kn"); + } + + public static String getVolumeUnit(long userId) { + return (String) Context.getPermissionsManager().lookupAttribute(userId, "volumeUnit", "ltr"); + } + + public static TimeZone getTimezone(long userId) { + String timezone = (String) Context.getPermissionsManager().lookupAttribute(userId, "timezone", null); + return timezone != null ? TimeZone.getTimeZone(timezone) : TimeZone.getDefault(); + } + + public static Collection getDeviceList(Collection deviceIds, Collection groupIds) { + Collection result = new LinkedHashSet<>(deviceIds); + for (long groupId : groupIds) { + result.addAll(Context.getPermissionsManager().getGroupDevices(groupId)); + } + return result; + } + + public static double calculateDistance(Position firstPosition, Position lastPosition) { + return calculateDistance(firstPosition, lastPosition, true); + } + + public static double calculateDistance(Position firstPosition, Position lastPosition, boolean useOdometer) { + double distance = 0.0; + double firstOdometer = firstPosition.getDouble(Position.KEY_ODOMETER); + double lastOdometer = lastPosition.getDouble(Position.KEY_ODOMETER); + + if (useOdometer && firstOdometer != 0.0 && lastOdometer != 0.0) { + distance = lastOdometer - firstOdometer; + } else if (firstPosition.getAttributes().containsKey(Position.KEY_TOTAL_DISTANCE) + && lastPosition.getAttributes().containsKey(Position.KEY_TOTAL_DISTANCE)) { + distance = lastPosition.getDouble(Position.KEY_TOTAL_DISTANCE) + - firstPosition.getDouble(Position.KEY_TOTAL_DISTANCE); + } + return distance; + } + + public static double calculateFuel(Position firstPosition, Position lastPosition) { + + if (firstPosition.getAttributes().get(Position.KEY_FUEL_LEVEL) != null + && lastPosition.getAttributes().get(Position.KEY_FUEL_LEVEL) != null) { + + BigDecimal value = BigDecimal.valueOf(firstPosition.getDouble(Position.KEY_FUEL_LEVEL) + - lastPosition.getDouble(Position.KEY_FUEL_LEVEL)); + return value.setScale(1, RoundingMode.HALF_EVEN).doubleValue(); + } + return 0; + } + + public static String findDriver(Position firstPosition, Position lastPosition) { + if (firstPosition.getAttributes().containsKey(Position.KEY_DRIVER_UNIQUE_ID)) { + return firstPosition.getString(Position.KEY_DRIVER_UNIQUE_ID); + } else if (lastPosition.getAttributes().containsKey(Position.KEY_DRIVER_UNIQUE_ID)) { + return lastPosition.getString(Position.KEY_DRIVER_UNIQUE_ID); + } + return null; + } + + public static String findDriverName(String driverUniqueId) { + if (driverUniqueId != null && Context.getDriversManager() != null) { + Driver driver = Context.getDriversManager().getDriverByUniqueId(driverUniqueId); + if (driver != null) { + return driver.getName(); + } + } + return null; + } + + public static org.jxls.common.Context initializeContext(long userId) { + org.jxls.common.Context jxlsContext = PoiTransformer.createInitialContext(); + jxlsContext.putVar("distanceUnit", getDistanceUnit(userId)); + jxlsContext.putVar("speedUnit", getSpeedUnit(userId)); + jxlsContext.putVar("volumeUnit", getVolumeUnit(userId)); + jxlsContext.putVar("webUrl", Context.getVelocityEngine().getProperty("web.url")); + jxlsContext.putVar("dateTool", new DateTool()); + jxlsContext.putVar("numberTool", new NumberTool()); + jxlsContext.putVar("timezone", getTimezone(userId)); + jxlsContext.putVar("locale", Locale.getDefault()); + jxlsContext.putVar("bracketsRegex", "[\\{\\}\"]"); + return jxlsContext; + } + + public static void processTemplateWithSheets( + InputStream templateStream, OutputStream targetStream, + org.jxls.common.Context jxlsContext) throws IOException { + + Transformer transformer = TransformerFactory.createTransformer(templateStream, targetStream); + List xlsAreas = new XlsCommentAreaBuilder(transformer).build(); + for (Area xlsArea : xlsAreas) { + xlsArea.applyAt(new CellRef(xlsArea.getStartCellRef().getCellName()), jxlsContext); + xlsArea.setFormulaProcessor(new StandardFormulaProcessor()); + xlsArea.processFormulas(); + } + transformer.deleteSheet(xlsAreas.get(0).getStartCellRef().getSheetName()); + transformer.write(); + } + + private static TripReport calculateTrip( + IdentityManager identityManager, ArrayList positions, + int startIndex, int endIndex, boolean ignoreOdometer) { + + Position startTrip = positions.get(startIndex); + Position endTrip = positions.get(endIndex); + + double speedMax = 0; + for (int i = startIndex; i <= endIndex; i++) { + double speed = positions.get(i).getSpeed(); + 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(identityManager.getById(deviceId).getName()); + + trip.setStartPositionId(startTrip.getId()); + trip.setStartLat(startTrip.getLatitude()); + trip.setStartLon(startTrip.getLongitude()); + trip.setStartTime(startTrip.getFixTime()); + String startAddress = startTrip.getAddress(); + if (startAddress == null && Context.getGeocoder() != null + && Context.getConfig().getBoolean(Keys.GEOCODER_ON_REQUEST)) { + startAddress = Context.getGeocoder().getAddress(startTrip.getLatitude(), startTrip.getLongitude(), null); + } + trip.setStartAddress(startAddress); + + trip.setEndPositionId(endTrip.getId()); + trip.setEndLat(endTrip.getLatitude()); + trip.setEndLon(endTrip.getLongitude()); + trip.setEndTime(endTrip.getFixTime()); + String endAddress = endTrip.getAddress(); + if (endAddress == null && Context.getGeocoder() != null + && Context.getConfig().getBoolean(Keys.GEOCODER_ON_REQUEST)) { + endAddress = Context.getGeocoder().getAddress(endTrip.getLatitude(), endTrip.getLongitude(), null); + } + trip.setEndAddress(endAddress); + + trip.setDistance(calculateDistance(startTrip, endTrip, !ignoreOdometer)); + trip.setDuration(tripDuration); + if (tripDuration > 0) { + trip.setAverageSpeed(UnitsConverter.knotsFromMps(trip.getDistance() * 1000 / tripDuration)); + } + trip.setMaxSpeed(speedMax); + trip.setSpentFuel(calculateFuel(startTrip, endTrip)); + + trip.setDriverUniqueId(findDriver(startTrip, endTrip)); + trip.setDriverName(findDriverName(trip.getDriverUniqueId())); + + if (!ignoreOdometer + && startTrip.getDouble(Position.KEY_ODOMETER) != 0 + && endTrip.getDouble(Position.KEY_ODOMETER) != 0) { + trip.setStartOdometer(startTrip.getDouble(Position.KEY_ODOMETER)); + trip.setEndOdometer(endTrip.getDouble(Position.KEY_ODOMETER)); + } else { + trip.setStartOdometer(startTrip.getDouble(Position.KEY_TOTAL_DISTANCE)); + trip.setEndOdometer(endTrip.getDouble(Position.KEY_TOTAL_DISTANCE)); + } + + return trip; + } + + private static StopReport calculateStop( + IdentityManager identityManager, ArrayList positions, + int startIndex, int endIndex, boolean ignoreOdometer) { + + Position startStop = positions.get(startIndex); + Position endStop = positions.get(endIndex); + + StopReport stop = new StopReport(); + + long deviceId = startStop.getDeviceId(); + stop.setDeviceId(deviceId); + stop.setDeviceName(identityManager.getById(deviceId).getName()); + + stop.setPositionId(startStop.getId()); + stop.setLatitude(startStop.getLatitude()); + stop.setLongitude(startStop.getLongitude()); + stop.setStartTime(startStop.getFixTime()); + String address = startStop.getAddress(); + if (address == null && Context.getGeocoder() != null + && Context.getConfig().getBoolean(Keys.GEOCODER_ON_REQUEST)) { + address = Context.getGeocoder().getAddress(stop.getLatitude(), stop.getLongitude(), null); + } + stop.setAddress(address); + + stop.setEndTime(endStop.getFixTime()); + + long stopDuration = endStop.getFixTime().getTime() - startStop.getFixTime().getTime(); + stop.setDuration(stopDuration); + stop.setSpentFuel(calculateFuel(startStop, endStop)); + + if (startStop.getAttributes().containsKey(Position.KEY_HOURS) + && endStop.getAttributes().containsKey(Position.KEY_HOURS)) { + stop.setEngineHours(endStop.getLong(Position.KEY_HOURS) - startStop.getLong(Position.KEY_HOURS)); + } + + if (!ignoreOdometer + && startStop.getDouble(Position.KEY_ODOMETER) != 0 + && endStop.getDouble(Position.KEY_ODOMETER) != 0) { + stop.setStartOdometer(startStop.getDouble(Position.KEY_ODOMETER)); + stop.setEndOdometer(endStop.getDouble(Position.KEY_ODOMETER)); + } else { + stop.setStartOdometer(startStop.getDouble(Position.KEY_TOTAL_DISTANCE)); + stop.setEndOdometer(endStop.getDouble(Position.KEY_TOTAL_DISTANCE)); + } + + return stop; + + } + + private static T calculateTripOrStop( + IdentityManager identityManager, ArrayList positions, + int startIndex, int endIndex, boolean ignoreOdometer, Class reportClass) { + + if (reportClass.equals(TripReport.class)) { + return (T) calculateTrip(identityManager, positions, startIndex, endIndex, ignoreOdometer); + } else { + return (T) calculateStop(identityManager, positions, startIndex, endIndex, ignoreOdometer); + } + } + + private static boolean isMoving(ArrayList positions, int index, TripsConfig tripsConfig) { + if (tripsConfig.getMinimalNoDataDuration() > 0) { + boolean beforeGap = index < positions.size() - 1 + && positions.get(index + 1).getFixTime().getTime() - positions.get(index).getFixTime().getTime() + >= tripsConfig.getMinimalNoDataDuration(); + boolean afterGap = index > 0 + && positions.get(index).getFixTime().getTime() - positions.get(index - 1).getFixTime().getTime() + >= tripsConfig.getMinimalNoDataDuration(); + if (beforeGap || afterGap) { + return false; + } + } + if (positions.get(index).getAttributes().containsKey(Position.KEY_MOTION) + && positions.get(index).getAttributes().get(Position.KEY_MOTION) instanceof Boolean) { + return positions.get(index).getBoolean(Position.KEY_MOTION); + } else { + return positions.get(index).getSpeed() > tripsConfig.getSpeedThreshold(); + } + } + + public static Collection detectTripsAndStops( + IdentityManager identityManager, DeviceManager deviceManager, + Collection positionCollection, + TripsConfig tripsConfig, boolean ignoreOdometer, Class reportClass) { + + Collection result = new ArrayList<>(); + + ArrayList positions = new ArrayList<>(positionCollection); + if (!positions.isEmpty()) { + boolean trips = reportClass.equals(TripReport.class); + MotionEventHandler motionHandler = new MotionEventHandler(identityManager, deviceManager, tripsConfig); + DeviceState deviceState = new DeviceState(); + deviceState.setMotionState(isMoving(positions, 0, tripsConfig)); + int startEventIndex = trips == deviceState.getMotionState() ? 0 : -1; + int startNoEventIndex = -1; + for (int i = 0; i < positions.size(); i++) { + Map event = motionHandler.updateMotionState(deviceState, positions.get(i), + isMoving(positions, i, tripsConfig)); + if (startEventIndex == -1 + && (trips != deviceState.getMotionState() && deviceState.getMotionPosition() != null + || trips == deviceState.getMotionState() && event != null)) { + startEventIndex = i; + startNoEventIndex = -1; + } else if (trips != deviceState.getMotionState() && startEventIndex != -1 + && deviceState.getMotionPosition() == null && event == null) { + startEventIndex = -1; + } + if (startNoEventIndex == -1 + && (trips == deviceState.getMotionState() && deviceState.getMotionPosition() != null + || trips != deviceState.getMotionState() && event != null)) { + startNoEventIndex = i; + } else if (startNoEventIndex != -1 && deviceState.getMotionPosition() == null && event == null) { + startNoEventIndex = -1; + } + if (startEventIndex != -1 && startNoEventIndex != -1 && event != null + && trips != deviceState.getMotionState()) { + result.add(calculateTripOrStop(identityManager, positions, + startEventIndex, startNoEventIndex, ignoreOdometer, reportClass)); + startEventIndex = -1; + } + } + if (startEventIndex != -1 && (startNoEventIndex != -1 || !trips)) { + result.add(calculateTripOrStop(identityManager, positions, + startEventIndex, startNoEventIndex != -1 ? startNoEventIndex : positions.size() - 1, + ignoreOdometer, reportClass)); + } + } + + return result; + } + +} diff --git a/src/main/java/org/traccar/reports/common/TripsConfig.java b/src/main/java/org/traccar/reports/common/TripsConfig.java new file mode 100644 index 000000000..9a7cebafb --- /dev/null +++ b/src/main/java/org/traccar/reports/common/TripsConfig.java @@ -0,0 +1,74 @@ +/* + * Copyright 2017 - 2022 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.common; + +public class TripsConfig { + + public TripsConfig(double minimalTripDistance, long minimalTripDuration, long minimalParkingDuration, + long minimalNoDataDuration, boolean useIgnition, boolean processInvalidPositions, double speedThreshold) { + this.minimalTripDistance = minimalTripDistance; + this.minimalTripDuration = minimalTripDuration; + this.minimalParkingDuration = minimalParkingDuration; + this.minimalNoDataDuration = minimalNoDataDuration; + this.useIgnition = useIgnition; + this.processInvalidPositions = processInvalidPositions; + this.speedThreshold = speedThreshold; + } + + private final double minimalTripDistance; + + public double getMinimalTripDistance() { + return minimalTripDistance; + } + + private final long minimalTripDuration; + + public long getMinimalTripDuration() { + return minimalTripDuration; + } + + private final long minimalParkingDuration; + + public long getMinimalParkingDuration() { + return minimalParkingDuration; + } + + private final long minimalNoDataDuration; + + public long getMinimalNoDataDuration() { + return minimalNoDataDuration; + } + + private final boolean useIgnition; + + public boolean getUseIgnition() { + return useIgnition; + } + + private final boolean processInvalidPositions; + + public boolean getProcessInvalidPositions() { + return processInvalidPositions; + } + + private final double speedThreshold; + + public double getSpeedThreshold() { + return speedThreshold; + } + +} diff --git a/src/main/java/org/traccar/reports/model/TripsConfig.java b/src/main/java/org/traccar/reports/model/TripsConfig.java deleted file mode 100644 index 34c445f8b..000000000 --- a/src/main/java/org/traccar/reports/model/TripsConfig.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright 2017 - 2022 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(double minimalTripDistance, long minimalTripDuration, long minimalParkingDuration, - long minimalNoDataDuration, boolean useIgnition, boolean processInvalidPositions, double speedThreshold) { - this.minimalTripDistance = minimalTripDistance; - this.minimalTripDuration = minimalTripDuration; - this.minimalParkingDuration = minimalParkingDuration; - this.minimalNoDataDuration = minimalNoDataDuration; - this.useIgnition = useIgnition; - this.processInvalidPositions = processInvalidPositions; - this.speedThreshold = speedThreshold; - } - - private final double minimalTripDistance; - - public double getMinimalTripDistance() { - return minimalTripDistance; - } - - private final long minimalTripDuration; - - public long getMinimalTripDuration() { - return minimalTripDuration; - } - - private final long minimalParkingDuration; - - public long getMinimalParkingDuration() { - return minimalParkingDuration; - } - - private final long minimalNoDataDuration; - - public long getMinimalNoDataDuration() { - return minimalNoDataDuration; - } - - private final boolean useIgnition; - - public boolean getUseIgnition() { - return useIgnition; - } - - private final boolean processInvalidPositions; - - public boolean getProcessInvalidPositions() { - return processInvalidPositions; - } - - private final double speedThreshold; - - public double getSpeedThreshold() { - return speedThreshold; - } - -} -- cgit v1.2.3