diff options
Diffstat (limited to 'src/main/java/org/traccar/reports')
27 files changed, 1695 insertions, 1049 deletions
diff --git a/src/main/java/org/traccar/reports/CombinedReportProvider.java b/src/main/java/org/traccar/reports/CombinedReportProvider.java new file mode 100644 index 000000000..bad3a61b3 --- /dev/null +++ b/src/main/java/org/traccar/reports/CombinedReportProvider.java @@ -0,0 +1,83 @@ +/* + * Copyright 2023 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. + * 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.traccar.helper.model.DeviceUtil; +import org.traccar.helper.model.PositionUtil; +import org.traccar.model.Device; +import org.traccar.model.Event; +import org.traccar.reports.common.ReportUtils; +import org.traccar.reports.model.CombinedReportItem; +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 jakarta.inject.Inject; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; +import java.util.Set; +import java.util.stream.Collectors; + +public class CombinedReportProvider { + + private static final Set<String> EXCLUDE_TYPES = Set.of(Event.TYPE_DEVICE_MOVING); + + private final ReportUtils reportUtils; + private final Storage storage; + + @Inject + public CombinedReportProvider(ReportUtils reportUtils, Storage storage) { + this.reportUtils = reportUtils; + this.storage = storage; + } + + public Collection<CombinedReportItem> getObjects( + long userId, Collection<Long> deviceIds, Collection<Long> groupIds, + Date from, Date to) throws StorageException { + reportUtils.checkPeriodLimit(from, to); + + ArrayList<CombinedReportItem> result = new ArrayList<>(); + for (Device device: DeviceUtil.getAccessibleDevices(storage, userId, deviceIds, groupIds)) { + CombinedReportItem item = new CombinedReportItem(); + item.setDeviceId(device.getId()); + var positions = PositionUtil.getPositions(storage, device.getId(), from, to); + item.setRoute(positions.stream() + .map(p -> new double[] {p.getLongitude(), p.getLatitude()}) + .collect(Collectors.toList())); + var events = storage.getObjects(Event.class, new Request( + new Columns.All(), + new Condition.And( + new Condition.Equals("deviceId", device.getId()), + new Condition.Between("eventTime", "from", from, "to", to)), + new Order("eventTime"))); + item.setEvents(events.stream() + .filter(e -> e.getPositionId() > 0 && !EXCLUDE_TYPES.contains(e.getType())) + .collect(Collectors.toList())); + var eventPositions = events.stream() + .map(Event::getPositionId) + .collect(Collectors.toSet()); + item.setPositions(positions.stream() + .filter(p -> eventPositions.contains(p.getId())) + .collect(Collectors.toList())); + result.add(item); + } + return result; + } +} diff --git a/src/main/java/org/traccar/reports/CsvExportProvider.java b/src/main/java/org/traccar/reports/CsvExportProvider.java new file mode 100644 index 000000000..521dc120a --- /dev/null +++ b/src/main/java/org/traccar/reports/CsvExportProvider.java @@ -0,0 +1,76 @@ +/* + * Copyright 2022 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. + * 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.traccar.helper.DateUtil; +import org.traccar.helper.model.PositionUtil; +import org.traccar.model.Position; +import org.traccar.storage.Storage; +import org.traccar.storage.StorageException; + +import jakarta.inject.Inject; +import java.io.OutputStream; +import java.io.PrintWriter; +import java.util.Date; +import java.util.LinkedHashMap; +import java.util.Objects; +import java.util.function.Function; +import java.util.stream.Collectors; + +public class CsvExportProvider { + + private final Storage storage; + + @Inject + public CsvExportProvider(Storage storage) { + this.storage = storage; + } + + public void generate( + OutputStream outputStream, long deviceId, Date from, Date to) throws StorageException { + + var positions = PositionUtil.getPositions(storage, deviceId, from, to); + + var attributes = positions.stream() + .flatMap((position -> position.getAttributes().keySet().stream())) + .collect(Collectors.toUnmodifiableSet()); + + var properties = new LinkedHashMap<String, Function<Position, Object>>(); + properties.put("id", Position::getId); + properties.put("deviceId", Position::getDeviceId); + properties.put("protocol", Position::getProtocol); + properties.put("serverTime", position -> DateUtil.formatDate(position.getServerTime())); + properties.put("deviceTime", position -> DateUtil.formatDate(position.getDeviceTime())); + properties.put("fixTime", position -> DateUtil.formatDate(position.getFixTime())); + properties.put("valid", Position::getValid); + properties.put("latitude", Position::getLatitude); + properties.put("longitude", Position::getLongitude); + properties.put("altitude", Position::getAltitude); + properties.put("speed", Position::getSpeed); + properties.put("course", Position::getCourse); + properties.put("address", Position::getAddress); + properties.put("accuracy", Position::getAccuracy); + attributes.forEach(key -> properties.put(key, position -> position.getAttributes().get(key))); + + try (PrintWriter writer = new PrintWriter(outputStream)) { + writer.println(String.join(",", properties.keySet())); + positions.forEach(position -> writer.println(properties.values().stream() + .map(f -> Objects.toString(f.apply(position), "")) + .collect(Collectors.joining(",")))); + } + } + +} diff --git a/src/main/java/org/traccar/reports/Events.java b/src/main/java/org/traccar/reports/Events.java deleted file mode 100644 index e4b905702..000000000 --- a/src/main/java/org/traccar/reports/Events.java +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Copyright 2016 - 2018 Anton Tananaev (anton@traccar.org) - * Copyright 2016 - 2018 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.util.ArrayList; -import java.util.Collection; -import java.util.Date; -import java.util.HashMap; -import java.util.Iterator; - -import org.apache.poi.ss.util.WorkbookUtil; -import org.traccar.Context; -import org.traccar.model.Device; -import org.traccar.model.Event; -import org.traccar.model.Geofence; -import org.traccar.model.Group; -import org.traccar.model.Maintenance; -import org.traccar.reports.model.DeviceReport; -import org.traccar.storage.StorageException; - -public final class Events { - - private Events() { - } - - public static Collection<Event> getObjects(long userId, Collection<Long> deviceIds, Collection<Long> groupIds, - Collection<String> types, Date from, Date to) throws StorageException { - ReportUtils.checkPeriodLimit(from, to); - ArrayList<Event> result = new ArrayList<>(); - for (long deviceId: ReportUtils.getDeviceList(deviceIds, groupIds)) { - Context.getPermissionsManager().checkDevice(userId, deviceId); - Collection<Event> events = Context.getDataManager().getEvents(deviceId, from, to); - boolean all = types.isEmpty() || types.contains(Event.ALL_EVENTS); - for (Event event : events) { - if (all || types.contains(event.getType())) { - long geofenceId = event.getGeofenceId(); - long maintenanceId = event.getMaintenanceId(); - if ((geofenceId == 0 || Context.getGeofenceManager().checkItemPermission(userId, geofenceId)) - && (maintenanceId == 0 - || Context.getMaintenancesManager().checkItemPermission(userId, maintenanceId))) { - result.add(event); - } - } - } - } - return result; - } - - public static void getExcel(OutputStream outputStream, - long userId, Collection<Long> deviceIds, Collection<Long> groupIds, - Collection<String> types, Date from, Date to) throws StorageException, IOException { - ReportUtils.checkPeriodLimit(from, to); - ArrayList<DeviceReport> devicesEvents = new ArrayList<>(); - ArrayList<String> sheetNames = new ArrayList<>(); - HashMap<Long, String> geofenceNames = new HashMap<>(); - HashMap<Long, String> maintenanceNames = new HashMap<>(); - for (long deviceId: ReportUtils.getDeviceList(deviceIds, groupIds)) { - Context.getPermissionsManager().checkDevice(userId, deviceId); - Collection<Event> events = Context.getDataManager().getEvents(deviceId, from, to); - boolean all = types.isEmpty() || types.contains(Event.ALL_EVENTS); - for (Iterator<Event> iterator = events.iterator(); iterator.hasNext();) { - Event event = iterator.next(); - if (all || types.contains(event.getType())) { - long geofenceId = event.getGeofenceId(); - long maintenanceId = event.getMaintenanceId(); - if (geofenceId != 0) { - if (Context.getGeofenceManager().checkItemPermission(userId, geofenceId)) { - Geofence geofence = Context.getGeofenceManager().getById(geofenceId); - if (geofence != null) { - geofenceNames.put(geofenceId, geofence.getName()); - } - } else { - iterator.remove(); - } - } else if (maintenanceId != 0) { - if (Context.getMaintenancesManager().checkItemPermission(userId, maintenanceId)) { - Maintenance maintenance = Context.getMaintenancesManager().getById(maintenanceId); - if (maintenance != null) { - maintenanceNames.put(maintenanceId, maintenance.getName()); - } - } else { - iterator.remove(); - } - } - } else { - iterator.remove(); - } - } - DeviceReport deviceEvents = new DeviceReport(); - Device device = Context.getIdentityManager().getById(deviceId); - deviceEvents.setDeviceName(device.getName()); - sheetNames.add(WorkbookUtil.createSafeSheetName(deviceEvents.getDeviceName())); - if (device.getGroupId() != 0) { - Group group = Context.getGroupsManager().getById(device.getGroupId()); - if (group != null) { - deviceEvents.setGroupName(group.getName()); - } - } - deviceEvents.setObjects(events); - devicesEvents.add(deviceEvents); - } - String templatePath = Context.getConfig().getString("report.templatesPath", - "templates/export/"); - try (InputStream inputStream = new FileInputStream(templatePath + "/events.xlsx")) { - org.jxls.common.Context jxlsContext = ReportUtils.initializeContext(userId); - jxlsContext.putVar("devices", devicesEvents); - jxlsContext.putVar("sheetNames", sheetNames); - jxlsContext.putVar("geofenceNames", geofenceNames); - jxlsContext.putVar("maintenanceNames", maintenanceNames); - jxlsContext.putVar("from", from); - jxlsContext.putVar("to", to); - ReportUtils.processTemplateWithSheets(inputStream, outputStream, jxlsContext); - } - } -} diff --git a/src/main/java/org/traccar/reports/EventsReportProvider.java b/src/main/java/org/traccar/reports/EventsReportProvider.java new file mode 100644 index 000000000..f252f28cc --- /dev/null +++ b/src/main/java/org/traccar/reports/EventsReportProvider.java @@ -0,0 +1,170 @@ +/* + * Copyright 2016 - 2022 Anton Tananaev (anton@traccar.org) + * Copyright 2016 - 2018 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.poi.ss.util.WorkbookUtil; +import org.traccar.config.Config; +import org.traccar.config.Keys; +import org.traccar.helper.model.DeviceUtil; +import org.traccar.model.Device; +import org.traccar.model.Event; +import org.traccar.model.Geofence; +import org.traccar.model.Group; +import org.traccar.model.Maintenance; +import org.traccar.model.Position; +import org.traccar.reports.common.ReportUtils; +import org.traccar.reports.model.DeviceReportSection; +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 jakarta.inject.Inject; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; + +public class EventsReportProvider { + + private final Config config; + private final ReportUtils reportUtils; + private final Storage storage; + + @Inject + public EventsReportProvider(Config config, ReportUtils reportUtils, Storage storage) { + this.config = config; + this.reportUtils = reportUtils; + this.storage = storage; + } + + private List<Event> getEvents(long deviceId, Date from, Date to) throws StorageException { + return storage.getObjects(Event.class, new Request( + new Columns.All(), + new Condition.And( + new Condition.Equals("deviceId", deviceId), + new Condition.Between("eventTime", "from", from, "to", to)), + new Order("eventTime"))); + } + + public Collection<Event> getObjects( + long userId, Collection<Long> deviceIds, Collection<Long> groupIds, + Collection<String> types, Date from, Date to) throws StorageException { + reportUtils.checkPeriodLimit(from, to); + + ArrayList<Event> result = new ArrayList<>(); + for (Device device: DeviceUtil.getAccessibleDevices(storage, userId, deviceIds, groupIds)) { + Collection<Event> events = getEvents(device.getId(), from, to); + boolean all = types.isEmpty() || types.contains(Event.ALL_EVENTS); + for (Event event : events) { + if (all || types.contains(event.getType())) { + long geofenceId = event.getGeofenceId(); + long maintenanceId = event.getMaintenanceId(); + if ((geofenceId == 0 || reportUtils.getObject(userId, Geofence.class, geofenceId) != null) + && (maintenanceId == 0 + || reportUtils.getObject(userId, Maintenance.class, maintenanceId) != null)) { + result.add(event); + } + } + } + } + return result; + } + + public void getExcel( + OutputStream outputStream, long userId, Collection<Long> deviceIds, Collection<Long> groupIds, + Collection<String> types, Date from, Date to) throws StorageException, IOException { + reportUtils.checkPeriodLimit(from, to); + + ArrayList<DeviceReportSection> devicesEvents = new ArrayList<>(); + ArrayList<String> sheetNames = new ArrayList<>(); + HashMap<Long, String> geofenceNames = new HashMap<>(); + HashMap<Long, String> maintenanceNames = new HashMap<>(); + HashMap<Long, Position> positions = new HashMap<>(); + for (Device device: DeviceUtil.getAccessibleDevices(storage, userId, deviceIds, groupIds)) { + Collection<Event> events = getEvents(device.getId(), from, to); + boolean all = types.isEmpty() || types.contains(Event.ALL_EVENTS); + for (Iterator<Event> iterator = events.iterator(); iterator.hasNext();) { + Event event = iterator.next(); + if (all || types.contains(event.getType())) { + long geofenceId = event.getGeofenceId(); + long maintenanceId = event.getMaintenanceId(); + if (geofenceId != 0) { + Geofence geofence = reportUtils.getObject(userId, Geofence.class, geofenceId); + if (geofence != null) { + geofenceNames.put(geofenceId, geofence.getName()); + } else { + iterator.remove(); + } + } else if (maintenanceId != 0) { + Maintenance maintenance = reportUtils.getObject(userId, Maintenance.class, maintenanceId); + if (maintenance != null) { + maintenanceNames.put(maintenanceId, maintenance.getName()); + } else { + iterator.remove(); + } + } + } else { + iterator.remove(); + } + } + for (Event event : events) { + long positionId = event.getPositionId(); + if (positionId > 0) { + Position position = storage.getObject(Position.class, new Request( + new Columns.All(), new Condition.Equals("id", positionId))); + positions.put(positionId, position); + } + } + DeviceReportSection deviceEvents = new DeviceReportSection(); + deviceEvents.setDeviceName(device.getName()); + sheetNames.add(WorkbookUtil.createSafeSheetName(deviceEvents.getDeviceName())); + if (device.getGroupId() > 0) { + Group group = storage.getObject(Group.class, new Request( + new Columns.All(), new Condition.Equals("id", device.getGroupId()))); + if (group != null) { + deviceEvents.setGroupName(group.getName()); + } + } + deviceEvents.setObjects(events); + devicesEvents.add(deviceEvents); + } + + File file = Paths.get(config.getString(Keys.TEMPLATES_ROOT), "export", "events.xlsx").toFile(); + try (InputStream inputStream = new FileInputStream(file)) { + var context = reportUtils.initializeContext(userId); + context.putVar("devices", devicesEvents); + context.putVar("sheetNames", sheetNames); + context.putVar("geofenceNames", geofenceNames); + context.putVar("maintenanceNames", maintenanceNames); + context.putVar("positions", positions); + context.putVar("from", from); + context.putVar("to", to); + reportUtils.processTemplateWithSheets(inputStream, outputStream, context); + } + } +} diff --git a/src/main/java/org/traccar/reports/GpxExportProvider.java b/src/main/java/org/traccar/reports/GpxExportProvider.java new file mode 100644 index 000000000..1c45b6416 --- /dev/null +++ b/src/main/java/org/traccar/reports/GpxExportProvider.java @@ -0,0 +1,76 @@ +/* + * Copyright 2022 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. + * 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.traccar.helper.DateUtil; +import org.traccar.helper.model.PositionUtil; +import org.traccar.model.Device; +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 jakarta.inject.Inject; +import java.io.OutputStream; +import java.io.PrintWriter; +import java.util.Date; + +public class GpxExportProvider { + + private final Storage storage; + + @Inject + public GpxExportProvider(Storage storage) { + this.storage = storage; + } + + public void generate( + OutputStream outputStream, long deviceId, Date from, Date to) throws StorageException { + + var device = storage.getObject(Device.class, new Request( + new Columns.All(), new Condition.Equals("id", deviceId))); + var positions = PositionUtil.getPositions(storage, deviceId, from, to); + + try (PrintWriter writer = new PrintWriter(outputStream)) { + writer.print("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"); + writer.print("<gpx version=\"1.0\">"); + writer.print("<trk>"); + writer.print("<name>"); + writer.print(device.getName()); + writer.print("</name>"); + writer.print("<trkseg>"); + positions.forEach(position -> { + writer.print("<trkpt lat=\""); + writer.print(position.getLatitude()); + writer.print("\" lon=\""); + writer.print(position.getLongitude()); + writer.print("\">"); + writer.print("<ele>"); + writer.print(position.getAltitude()); + writer.print("</ele>"); + writer.print("<time>"); + writer.print(DateUtil.formatDate(position.getFixTime())); + writer.print("</time>"); + writer.print("</trkpt>"); + }); + writer.print("</trkseg>"); + writer.print("</trk>"); + writer.print("</gpx>"); + } + } + +} diff --git a/src/main/java/org/traccar/reports/KmlExportProvider.java b/src/main/java/org/traccar/reports/KmlExportProvider.java new file mode 100644 index 000000000..24dca018c --- /dev/null +++ b/src/main/java/org/traccar/reports/KmlExportProvider.java @@ -0,0 +1,80 @@ +/* + * Copyright 2022 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. + * 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.traccar.helper.model.PositionUtil; +import org.traccar.model.Device; +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 jakarta.inject.Inject; +import java.io.OutputStream; +import java.io.PrintWriter; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.stream.Collectors; + +public class KmlExportProvider { + + private final Storage storage; + + @Inject + public KmlExportProvider(Storage storage) { + this.storage = storage; + } + + public void generate( + OutputStream outputStream, long deviceId, Date from, Date to) throws StorageException { + + var device = storage.getObject(Device.class, new Request( + new Columns.All(), new Condition.Equals("id", deviceId))); + var positions = PositionUtil.getPositions(storage, deviceId, from, to); + + var dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm"); + + try (PrintWriter writer = new PrintWriter(outputStream)) { + writer.print("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"); + writer.print("<kml xmlns=\"http://www.opengis.net/kml/2.2\">"); + writer.print("<Document>"); + writer.print("<name>"); + writer.print(device.getName()); + writer.print("</name>"); + writer.print("<Placemark>"); + writer.print("<name>"); + writer.print(dateFormat.format(from)); + writer.print(" - "); + writer.print(dateFormat.format(to)); + writer.print("</name>"); + writer.print("<LineString>"); + writer.print("<extrude>1</extrude>"); + writer.print("<tessellate>1</tessellate>"); + writer.print("<altitudeMode>absolute</altitudeMode>"); + writer.print("<coordinates>"); + writer.print(positions.stream() + .map((p -> String.format("%f,%f,%f", p.getLongitude(), p.getLatitude(), p.getAltitude()))) + .collect(Collectors.joining(" "))); + writer.print("</coordinates>"); + writer.print("</LineString>"); + writer.print("</Placemark>"); + writer.print("</Document>"); + writer.print("</kml>"); + } + } + +} 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 58674beae..000000000 --- a/src/main/java/org/traccar/reports/ReportUtils.java +++ /dev/null @@ -1,368 +0,0 @@ -/* - * Copyright 2016 - 2020 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.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 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.List; -import java.util.Locale; -import java.util.Map; -import java.util.TimeZone; - -public final class ReportUtils { - - private ReportUtils() { - } - - 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<Long> getDeviceList(Collection<Long> deviceIds, Collection<Long> groupIds) { - Collection<Long> result = new ArrayList<>(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<Area> 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( - ArrayList<Position> 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(Context.getIdentityManager().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( - ArrayList<Position> 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(Context.getIdentityManager().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 extends BaseReport> T calculateTripOrStop( - ArrayList<Position> positions, int startIndex, int endIndex, boolean ignoreOdometer, Class<T> reportClass) { - - if (reportClass.equals(TripReport.class)) { - return (T) calculateTrip(positions, startIndex, endIndex, ignoreOdometer); - } else { - return (T) calculateStop(positions, startIndex, endIndex, ignoreOdometer); - } - } - - private static boolean isMoving(ArrayList<Position> 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 <T extends BaseReport> Collection<T> detectTripsAndStops( - IdentityManager identityManager, DeviceManager deviceManager, - Collection<Position> positionCollection, - TripsConfig tripsConfig, boolean ignoreOdometer, Class<T> reportClass) { - - Collection<T> result = new ArrayList<>(); - - ArrayList<Position> 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, Position> 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(positions, startEventIndex, startNoEventIndex, - ignoreOdometer, reportClass)); - startEventIndex = -1; - } - } - if (startEventIndex != -1 && (startNoEventIndex != -1 || !trips)) { - result.add(calculateTripOrStop(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 deleted file mode 100644 index 4a5edd295..000000000 --- a/src/main/java/org/traccar/reports/Route.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright 2016 Anton Tananaev (anton@traccar.org) - * Copyright 2016 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.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.model.Position; -import org.traccar.reports.model.DeviceReport; -import org.traccar.storage.StorageException; - -public final class Route { - - private Route() { - } - - public static Collection<Position> getObjects(long userId, Collection<Long> deviceIds, Collection<Long> groupIds, - Date from, Date to) throws StorageException { - ReportUtils.checkPeriodLimit(from, to); - ArrayList<Position> result = new ArrayList<>(); - for (long deviceId: ReportUtils.getDeviceList(deviceIds, groupIds)) { - Context.getPermissionsManager().checkDevice(userId, deviceId); - result.addAll(Context.getDataManager().getPositions(deviceId, from, to)); - } - return result; - } - - public static void getExcel(OutputStream outputStream, - long userId, Collection<Long> deviceIds, Collection<Long> groupIds, - Date from, Date to) throws StorageException, IOException { - ReportUtils.checkPeriodLimit(from, to); - ArrayList<DeviceReport> devicesRoutes = new ArrayList<>(); - ArrayList<String> sheetNames = new ArrayList<>(); - for (long deviceId: ReportUtils.getDeviceList(deviceIds, groupIds)) { - Context.getPermissionsManager().checkDevice(userId, deviceId); - Collection<Position> positions = Context.getDataManager() - .getPositions(deviceId, from, to); - DeviceReport deviceRoutes = new DeviceReport(); - Device device = Context.getIdentityManager().getById(deviceId); - deviceRoutes.setDeviceName(device.getName()); - sheetNames.add(WorkbookUtil.createSafeSheetName(deviceRoutes.getDeviceName())); - if (device.getGroupId() != 0) { - Group group = Context.getGroupsManager().getById(device.getGroupId()); - if (group != null) { - deviceRoutes.setGroupName(group.getName()); - } - } - deviceRoutes.setObjects(positions); - devicesRoutes.add(deviceRoutes); - } - String templatePath = Context.getConfig().getString("report.templatesPath", - "templates/export/"); - try (InputStream inputStream = new FileInputStream(templatePath + "/route.xlsx")) { - org.jxls.common.Context jxlsContext = ReportUtils.initializeContext(userId); - jxlsContext.putVar("devices", devicesRoutes); - jxlsContext.putVar("sheetNames", sheetNames); - jxlsContext.putVar("from", from); - jxlsContext.putVar("to", to); - ReportUtils.processTemplateWithSheets(inputStream, outputStream, jxlsContext); - } - } -} diff --git a/src/main/java/org/traccar/reports/RouteReportProvider.java b/src/main/java/org/traccar/reports/RouteReportProvider.java new file mode 100644 index 000000000..d761fe1e5 --- /dev/null +++ b/src/main/java/org/traccar/reports/RouteReportProvider.java @@ -0,0 +1,113 @@ +/* + * Copyright 2016 - 2022 Anton Tananaev (anton@traccar.org) + * Copyright 2016 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.poi.ss.util.WorkbookUtil; +import org.traccar.config.Config; +import org.traccar.config.Keys; +import org.traccar.helper.model.DeviceUtil; +import org.traccar.helper.model.PositionUtil; +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.DeviceReportSection; +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 jakarta.inject.Inject; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; +import java.util.Map; +import java.util.HashMap; + +public class RouteReportProvider { + + private final Config config; + private final ReportUtils reportUtils; + private final Storage storage; + + private final Map<String, Integer> namesCount = new HashMap<>(); + + @Inject + public RouteReportProvider(Config config, ReportUtils reportUtils, Storage storage) { + this.config = config; + this.reportUtils = reportUtils; + this.storage = storage; + } + + public Collection<Position> getObjects(long userId, Collection<Long> deviceIds, Collection<Long> groupIds, + Date from, Date to) throws StorageException { + reportUtils.checkPeriodLimit(from, to); + + ArrayList<Position> result = new ArrayList<>(); + for (Device device: DeviceUtil.getAccessibleDevices(storage, userId, deviceIds, groupIds)) { + result.addAll(PositionUtil.getPositions(storage, device.getId(), from, to)); + } + return result; + } + + + private String getUniqueSheetName(String key) { + namesCount.compute(key, (k, value) -> value == null ? 1 : (value + 1)); + return namesCount.get(key) > 1 ? key + '-' + namesCount.get(key) : key; + } + + public void getExcel(OutputStream outputStream, + long userId, Collection<Long> deviceIds, Collection<Long> groupIds, + Date from, Date to) throws StorageException, IOException { + reportUtils.checkPeriodLimit(from, to); + + ArrayList<DeviceReportSection> devicesRoutes = new ArrayList<>(); + ArrayList<String> sheetNames = new ArrayList<>(); + for (Device device: DeviceUtil.getAccessibleDevices(storage, userId, deviceIds, groupIds)) { + var positions = PositionUtil.getPositions(storage, device.getId(), from, to); + DeviceReportSection deviceRoutes = new DeviceReportSection(); + deviceRoutes.setDeviceName(device.getName()); + sheetNames.add(WorkbookUtil.createSafeSheetName(getUniqueSheetName(deviceRoutes.getDeviceName()))); + if (device.getGroupId() > 0) { + Group group = storage.getObject(Group.class, new Request( + new Columns.All(), new Condition.Equals("id", device.getGroupId()))); + if (group != null) { + deviceRoutes.setGroupName(group.getName()); + } + } + deviceRoutes.setObjects(positions); + devicesRoutes.add(deviceRoutes); + } + + File file = Paths.get(config.getString(Keys.TEMPLATES_ROOT), "export", "route.xlsx").toFile(); + try (InputStream inputStream = new FileInputStream(file)) { + var context = reportUtils.initializeContext(userId); + context.putVar("devices", devicesRoutes); + context.putVar("sheetNames", sheetNames); + context.putVar("from", from); + context.putVar("to", to); + reportUtils.processTemplateWithSheets(inputStream, outputStream, context); + } + } +} diff --git a/src/main/java/org/traccar/reports/Stops.java b/src/main/java/org/traccar/reports/Stops.java deleted file mode 100644 index 36a4a7549..000000000 --- a/src/main/java/org/traccar/reports/Stops.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * 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.util.ArrayList; -import java.util.Collection; -import java.util.Date; - -import org.apache.poi.ss.util.WorkbookUtil; -import org.traccar.Context; -import org.traccar.Main; -import org.traccar.database.DeviceManager; -import org.traccar.database.IdentityManager; -import org.traccar.model.Device; -import org.traccar.model.Group; -import org.traccar.reports.model.DeviceReport; -import org.traccar.reports.model.StopReport; -import org.traccar.storage.StorageException; - -public final class Stops { - - private Stops() { - } - - private static Collection<StopReport> detectStops(long deviceId, Date from, Date to) throws StorageException { - boolean ignoreOdometer = Context.getDeviceManager() - .lookupAttributeBoolean(deviceId, "report.ignoreOdometer", false, false, true); - - IdentityManager identityManager = Main.getInjector().getInstance(IdentityManager.class); - DeviceManager deviceManager = Main.getInjector().getInstance(DeviceManager.class); - - return ReportUtils.detectTripsAndStops( - identityManager, deviceManager, Context.getDataManager().getPositions(deviceId, from, to), - Context.getTripsConfig(), ignoreOdometer, StopReport.class); - } - - public static Collection<StopReport> getObjects( - long userId, Collection<Long> deviceIds, Collection<Long> groupIds, - Date from, Date to) throws StorageException { - ReportUtils.checkPeriodLimit(from, to); - 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 StorageException, IOException { - ReportUtils.checkPeriodLimit(from, to); - 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().getById(deviceId); - deviceStops.setDeviceName(device.getName()); - sheetNames.add(WorkbookUtil.createSafeSheetName(deviceStops.getDeviceName())); - if (device.getGroupId() != 0) { - Group group = Context.getGroupsManager().getById(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/main/java/org/traccar/reports/StopsReportProvider.java b/src/main/java/org/traccar/reports/StopsReportProvider.java new file mode 100644 index 000000000..2160fec0e --- /dev/null +++ b/src/main/java/org/traccar/reports/StopsReportProvider.java @@ -0,0 +1,104 @@ +/* + * 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; + +import org.apache.poi.ss.util.WorkbookUtil; +import org.traccar.config.Config; +import org.traccar.config.Keys; +import org.traccar.helper.model.DeviceUtil; +import org.traccar.model.Device; +import org.traccar.model.Group; +import org.traccar.reports.common.ReportUtils; +import org.traccar.reports.model.DeviceReportSection; +import org.traccar.reports.model.StopReportItem; +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 jakarta.inject.Inject; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; + +public class StopsReportProvider { + + private final Config config; + private final ReportUtils reportUtils; + private final Storage storage; + + @Inject + public StopsReportProvider(Config config, ReportUtils reportUtils, Storage storage) { + this.config = config; + this.reportUtils = reportUtils; + this.storage = storage; + } + + public Collection<StopReportItem> getObjects( + long userId, Collection<Long> deviceIds, Collection<Long> groupIds, + Date from, Date to) throws StorageException { + reportUtils.checkPeriodLimit(from, to); + + ArrayList<StopReportItem> result = new ArrayList<>(); + for (Device device: DeviceUtil.getAccessibleDevices(storage, userId, deviceIds, groupIds)) { + result.addAll(reportUtils.detectTripsAndStops(device, from, to, StopReportItem.class)); + } + return result; + } + + public void getExcel( + OutputStream outputStream, long userId, Collection<Long> deviceIds, Collection<Long> groupIds, + Date from, Date to) throws StorageException, IOException { + reportUtils.checkPeriodLimit(from, to); + + ArrayList<DeviceReportSection> devicesStops = new ArrayList<>(); + ArrayList<String> sheetNames = new ArrayList<>(); + for (Device device: DeviceUtil.getAccessibleDevices(storage, userId, deviceIds, groupIds)) { + Collection<StopReportItem> stops = reportUtils.detectTripsAndStops(device, from, to, StopReportItem.class); + DeviceReportSection deviceStops = new DeviceReportSection(); + deviceStops.setDeviceName(device.getName()); + sheetNames.add(WorkbookUtil.createSafeSheetName(deviceStops.getDeviceName())); + if (device.getGroupId() > 0) { + Group group = storage.getObject(Group.class, new Request( + new Columns.All(), new Condition.Equals("id", device.getGroupId()))); + if (group != null) { + deviceStops.setGroupName(group.getName()); + } + } + deviceStops.setObjects(stops); + devicesStops.add(deviceStops); + } + + File file = Paths.get(config.getString(Keys.TEMPLATES_ROOT), "export", "stops.xlsx").toFile(); + try (InputStream inputStream = new FileInputStream(file)) { + var context = reportUtils.initializeContext(userId); + context.putVar("devices", devicesStops); + context.putVar("sheetNames", sheetNames); + context.putVar("from", from); + context.putVar("to", to); + reportUtils.processTemplateWithSheets(inputStream, outputStream, context); + } + } + +} diff --git a/src/main/java/org/traccar/reports/Summary.java b/src/main/java/org/traccar/reports/Summary.java deleted file mode 100644 index 4924af062..000000000 --- a/src/main/java/org/traccar/reports/Summary.java +++ /dev/null @@ -1,151 +0,0 @@ -/* - * Copyright 2016 - 2020 Anton Tananaev (anton@traccar.org) - * Copyright 2016 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.util.ArrayList; -import java.util.Calendar; -import java.util.Collection; -import java.util.Date; - -import org.jxls.util.JxlsHelper; -import org.traccar.Context; -import org.traccar.helper.UnitsConverter; -import org.traccar.model.Position; -import org.traccar.reports.model.SummaryReport; -import org.traccar.storage.StorageException; - -public final class Summary { - - private Summary() { - } - - private static SummaryReport calculateSummaryResult(long deviceId, Collection<Position> positions) { - SummaryReport result = new SummaryReport(); - result.setDeviceId(deviceId); - result.setDeviceName(Context.getIdentityManager().getById(deviceId).getName()); - if (positions != null && !positions.isEmpty()) { - Position firstPosition = null; - Position previousPosition = null; - for (Position position : positions) { - if (firstPosition == null) { - firstPosition = position; - } - previousPosition = position; - if (position.getSpeed() > result.getMaxSpeed()) { - result.setMaxSpeed(position.getSpeed()); - } - } - boolean ignoreOdometer = Context.getDeviceManager() - .lookupAttributeBoolean(deviceId, "report.ignoreOdometer", false, false, true); - result.setDistance(ReportUtils.calculateDistance(firstPosition, previousPosition, !ignoreOdometer)); - result.setSpentFuel(ReportUtils.calculateFuel(firstPosition, previousPosition)); - - long durationMilliseconds; - if (firstPosition.getAttributes().containsKey(Position.KEY_HOURS) - && previousPosition.getAttributes().containsKey(Position.KEY_HOURS)) { - durationMilliseconds = - previousPosition.getLong(Position.KEY_HOURS) - firstPosition.getLong(Position.KEY_HOURS); - result.setEngineHours(durationMilliseconds); - } else { - durationMilliseconds = - previousPosition.getFixTime().getTime() - firstPosition.getFixTime().getTime(); - } - - if (durationMilliseconds > 0) { - 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)); - } else { - result.setStartOdometer(firstPosition.getDouble(Position.KEY_TOTAL_DISTANCE)); - result.setEndOdometer(previousPosition.getDouble(Position.KEY_TOTAL_DISTANCE)); - } - - result.setStartTime(firstPosition.getFixTime()); - result.setEndTime(previousPosition.getFixTime()); - } - return result; - } - - private static int getDay(long userId, Date date) { - Calendar calendar = Calendar.getInstance(ReportUtils.getTimezone(userId)); - calendar.setTime(date); - return calendar.get(Calendar.DAY_OF_MONTH); - } - - private static Collection<SummaryReport> calculateSummaryResults( - long userId, long deviceId, Date from, Date to, boolean daily) throws StorageException { - - ArrayList<Position> positions = new ArrayList<>(Context.getDataManager().getPositions(deviceId, from, to)); - - ArrayList<SummaryReport> 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(deviceId, positions.subList(startIndex, i))); - startIndex = i; - startDay = currentDay; - } - } - results.add(calculateSummaryResult(deviceId, positions.subList(startIndex, positions.size()))); - } else { - results.add(calculateSummaryResult(deviceId, positions)); - } - - return results; - } - - public static Collection<SummaryReport> getObjects(long userId, Collection<Long> deviceIds, - Collection<Long> groupIds, Date from, Date to, boolean daily) throws StorageException { - ReportUtils.checkPeriodLimit(from, to); - ArrayList<SummaryReport> result = new ArrayList<>(); - for (long deviceId: ReportUtils.getDeviceList(deviceIds, groupIds)) { - Context.getPermissionsManager().checkDevice(userId, deviceId); - result.addAll(calculateSummaryResults(userId, deviceId, from, to, daily)); - } - return result; - } - - public static void getExcel(OutputStream outputStream, - long userId, Collection<Long> deviceIds, Collection<Long> groupIds, - Date from, Date to, boolean daily) throws StorageException, IOException { - ReportUtils.checkPeriodLimit(from, to); - Collection<SummaryReport> summaries = getObjects(userId, deviceIds, groupIds, from, to, daily); - String templatePath = Context.getConfig().getString("report.templatesPath", - "templates/export/"); - try (InputStream inputStream = new FileInputStream(templatePath + "/summary.xlsx")) { - org.jxls.common.Context jxlsContext = ReportUtils.initializeContext(userId); - jxlsContext.putVar("summaries", summaries); - jxlsContext.putVar("from", from); - jxlsContext.putVar("to", to); - JxlsHelper.getInstance().setUseFastFormulaProcessor(false) - .processTemplate(inputStream, outputStream, jxlsContext); - } - } -} diff --git a/src/main/java/org/traccar/reports/SummaryReportProvider.java b/src/main/java/org/traccar/reports/SummaryReportProvider.java new file mode 100644 index 000000000..ffde0b067 --- /dev/null +++ b/src/main/java/org/traccar/reports/SummaryReportProvider.java @@ -0,0 +1,192 @@ +/* + * 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"); + * 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.jxls.util.JxlsHelper; +import org.traccar.api.security.PermissionsService; +import org.traccar.config.Config; +import org.traccar.config.Keys; +import org.traccar.helper.UnitsConverter; +import org.traccar.helper.model.DeviceUtil; +import org.traccar.helper.model.PositionUtil; +import org.traccar.helper.model.UserUtil; +import org.traccar.model.Device; +import org.traccar.model.Position; +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 jakarta.inject.Inject; +import java.io.File; +import java.io.FileInputStream; +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.Collection; +import java.util.Date; +import java.util.List; + +public class SummaryReportProvider { + + private final Config config; + private final ReportUtils reportUtils; + private final PermissionsService permissionsService; + private final Storage storage; + + @Inject + public SummaryReportProvider( + Config config, ReportUtils reportUtils, PermissionsService permissionsService, Storage storage) { + this.config = config; + this.reportUtils = reportUtils; + this.permissionsService = permissionsService; + this.storage = storage; + } + + 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<SummaryReportItem> calculateDeviceResult( + Device device, Date from, Date to, boolean fast) throws StorageException { + + SummaryReportItem result = new SummaryReportItem(); + result.setDeviceId(device.getId()); + result.setDeviceName(device.getName()); + + 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 (first == null) { + first = 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(first, last, !ignoreOdometer)); + result.setSpentFuel(reportUtils.calculateFuel(first, last)); + + long durationMilliseconds; + 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 = last.getFixTime().getTime() - first.getFixTime().getTime(); + } + + if (durationMilliseconds > 0) { + result.setAverageSpeed(UnitsConverter.knotsFromMps(result.getDistance() * 1000 / durationMilliseconds)); + } + + if (!ignoreOdometer + && 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(first.getDouble(Position.KEY_TOTAL_DISTANCE)); + result.setEndOdometer(last.getDouble(Position.KEY_TOTAL_DISTANCE)); + } + + result.setStartTime(first.getFixTime()); + result.setEndTime(last.getFixTime()); + return List.of(result); + } + + return List.of(); + } + + private Collection<SummaryReportItem> calculateDeviceResults( + Device device, ZonedDateTime from, ZonedDateTime to, boolean daily) throws StorageException { + + boolean fast = Duration.between(from, to).toSeconds() > config.getLong(Keys.REPORT_FAST_THRESHOLD); + var results = new ArrayList<SummaryReportItem>(); + 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.addAll(calculateDeviceResult(device, Date.from(from.toInstant()), Date.from(to.toInstant()), fast)); + } else { + results.addAll(calculateDeviceResult(device, Date.from(from.toInstant()), Date.from(to.toInstant()), fast)); + } + return results; + } + + public Collection<SummaryReportItem> getObjects( + long userId, Collection<Long> deviceIds, Collection<Long> groupIds, + Date from, Date to, boolean daily) throws StorageException { + reportUtils.checkPeriodLimit(from, to); + + var tz = UserUtil.getTimezone(permissionsService.getServer(), permissionsService.getUser(userId)).toZoneId(); + + ArrayList<SummaryReportItem> result = new ArrayList<>(); + for (Device device: DeviceUtil.getAccessibleDevices(storage, userId, deviceIds, groupIds)) { + 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); + } + } + } + return result; + } + + public void getExcel(OutputStream outputStream, + long userId, Collection<Long> deviceIds, Collection<Long> groupIds, + Date from, Date to, boolean daily) throws StorageException, IOException { + Collection<SummaryReportItem> summaries = getObjects(userId, deviceIds, groupIds, from, to, daily); + + File file = Paths.get(config.getString(Keys.TEMPLATES_ROOT), "export", "summary.xlsx").toFile(); + try (InputStream inputStream = new FileInputStream(file)) { + var context = reportUtils.initializeContext(userId); + context.putVar("summaries", summaries); + context.putVar("from", from); + context.putVar("to", to); + JxlsHelper.getInstance().setUseFastFormulaProcessor(false) + .processTemplate(inputStream, outputStream, context); + } + } +} diff --git a/src/main/java/org/traccar/reports/Trips.java b/src/main/java/org/traccar/reports/Trips.java deleted file mode 100644 index 1461b869e..000000000 --- a/src/main/java/org/traccar/reports/Trips.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright 2016 Anton Tananaev (anton@traccar.org) - * Copyright 2016 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.util.ArrayList; -import java.util.Collection; -import java.util.Date; - -import org.apache.poi.ss.util.WorkbookUtil; -import org.traccar.Context; -import org.traccar.Main; -import org.traccar.database.DeviceManager; -import org.traccar.database.IdentityManager; -import org.traccar.model.Device; -import org.traccar.model.Group; -import org.traccar.reports.model.DeviceReport; -import org.traccar.reports.model.TripReport; -import org.traccar.storage.StorageException; - -public final class Trips { - - private Trips() { - } - - private static Collection<TripReport> detectTrips(long deviceId, Date from, Date to) throws StorageException { - boolean ignoreOdometer = Context.getDeviceManager() - .lookupAttributeBoolean(deviceId, "report.ignoreOdometer", false, false, true); - - IdentityManager identityManager = Main.getInjector().getInstance(IdentityManager.class); - DeviceManager deviceManager = Main.getInjector().getInstance(DeviceManager.class); - - return ReportUtils.detectTripsAndStops( - identityManager, deviceManager, Context.getDataManager().getPositions(deviceId, from, to), - Context.getTripsConfig(), ignoreOdometer, TripReport.class); - } - - public static Collection<TripReport> getObjects(long userId, Collection<Long> deviceIds, Collection<Long> groupIds, - Date from, Date to) throws StorageException { - ReportUtils.checkPeriodLimit(from, to); - ArrayList<TripReport> result = new ArrayList<>(); - for (long deviceId: ReportUtils.getDeviceList(deviceIds, groupIds)) { - Context.getPermissionsManager().checkDevice(userId, deviceId); - result.addAll(detectTrips(deviceId, from, to)); - } - return result; - } - - public static void getExcel(OutputStream outputStream, - long userId, Collection<Long> deviceIds, Collection<Long> groupIds, - Date from, Date to) throws StorageException, IOException { - ReportUtils.checkPeriodLimit(from, to); - ArrayList<DeviceReport> devicesTrips = new ArrayList<>(); - ArrayList<String> sheetNames = new ArrayList<>(); - for (long deviceId: ReportUtils.getDeviceList(deviceIds, groupIds)) { - Context.getPermissionsManager().checkDevice(userId, deviceId); - Collection<TripReport> trips = detectTrips(deviceId, from, to); - DeviceReport deviceTrips = new DeviceReport(); - Device device = Context.getIdentityManager().getById(deviceId); - deviceTrips.setDeviceName(device.getName()); - sheetNames.add(WorkbookUtil.createSafeSheetName(deviceTrips.getDeviceName())); - if (device.getGroupId() != 0) { - Group group = Context.getGroupsManager().getById(device.getGroupId()); - if (group != null) { - deviceTrips.setGroupName(group.getName()); - } - } - deviceTrips.setObjects(trips); - devicesTrips.add(deviceTrips); - } - String templatePath = Context.getConfig().getString("report.templatesPath", - "templates/export/"); - try (InputStream inputStream = new FileInputStream(templatePath + "/trips.xlsx")) { - org.jxls.common.Context jxlsContext = ReportUtils.initializeContext(userId); - jxlsContext.putVar("devices", devicesTrips); - jxlsContext.putVar("sheetNames", sheetNames); - jxlsContext.putVar("from", from); - jxlsContext.putVar("to", to); - ReportUtils.processTemplateWithSheets(inputStream, outputStream, jxlsContext); - } - } - -} diff --git a/src/main/java/org/traccar/reports/TripsReportProvider.java b/src/main/java/org/traccar/reports/TripsReportProvider.java new file mode 100644 index 000000000..9ff7232af --- /dev/null +++ b/src/main/java/org/traccar/reports/TripsReportProvider.java @@ -0,0 +1,104 @@ +/* + * Copyright 2016 - 2022 Anton Tananaev (anton@traccar.org) + * Copyright 2016 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.poi.ss.util.WorkbookUtil; +import org.traccar.config.Config; +import org.traccar.config.Keys; +import org.traccar.helper.model.DeviceUtil; +import org.traccar.model.Device; +import org.traccar.model.Group; +import org.traccar.reports.common.ReportUtils; +import org.traccar.reports.model.DeviceReportSection; +import org.traccar.reports.model.TripReportItem; +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 jakarta.inject.Inject; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; + +public class TripsReportProvider { + + private final Config config; + private final ReportUtils reportUtils; + private final Storage storage; + + @Inject + public TripsReportProvider(Config config, ReportUtils reportUtils, Storage storage) { + this.config = config; + this.reportUtils = reportUtils; + this.storage = storage; + } + + public Collection<TripReportItem> getObjects( + long userId, Collection<Long> deviceIds, Collection<Long> groupIds, + Date from, Date to) throws StorageException { + reportUtils.checkPeriodLimit(from, to); + + ArrayList<TripReportItem> result = new ArrayList<>(); + for (Device device: DeviceUtil.getAccessibleDevices(storage, userId, deviceIds, groupIds)) { + result.addAll(reportUtils.detectTripsAndStops(device, from, to, TripReportItem.class)); + } + return result; + } + + public void getExcel(OutputStream outputStream, + long userId, Collection<Long> deviceIds, Collection<Long> groupIds, + Date from, Date to) throws StorageException, IOException { + reportUtils.checkPeriodLimit(from, to); + + ArrayList<DeviceReportSection> devicesTrips = new ArrayList<>(); + ArrayList<String> sheetNames = new ArrayList<>(); + for (Device device: DeviceUtil.getAccessibleDevices(storage, userId, deviceIds, groupIds)) { + Collection<TripReportItem> trips = reportUtils.detectTripsAndStops(device, from, to, TripReportItem.class); + DeviceReportSection deviceTrips = new DeviceReportSection(); + deviceTrips.setDeviceName(device.getName()); + sheetNames.add(WorkbookUtil.createSafeSheetName(deviceTrips.getDeviceName())); + if (device.getGroupId() > 0) { + Group group = storage.getObject(Group.class, new Request( + new Columns.All(), new Condition.Equals("id", device.getGroupId()))); + if (group != null) { + deviceTrips.setGroupName(group.getName()); + } + } + deviceTrips.setObjects(trips); + devicesTrips.add(deviceTrips); + } + + File file = Paths.get(config.getString(Keys.TEMPLATES_ROOT), "export", "trips.xlsx").toFile(); + try (InputStream inputStream = new FileInputStream(file)) { + var context = reportUtils.initializeContext(userId); + context.putVar("devices", devicesTrips); + context.putVar("sheetNames", sheetNames); + context.putVar("from", from); + context.putVar("to", to); + reportUtils.processTemplateWithSheets(inputStream, outputStream, context); + } + } + +} diff --git a/src/main/java/org/traccar/reports/common/ExpressionEvaluatorFactory.java b/src/main/java/org/traccar/reports/common/ExpressionEvaluatorFactory.java new file mode 100644 index 000000000..8b139a572 --- /dev/null +++ b/src/main/java/org/traccar/reports/common/ExpressionEvaluatorFactory.java @@ -0,0 +1,58 @@ +package org.traccar.reports.common; + +import org.apache.commons.jexl3.JexlBuilder; +import org.apache.commons.jexl3.introspection.JexlPermissions; +import org.jxls.expression.ExpressionEvaluator; +import org.jxls.expression.JexlExpressionEvaluator; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +public class ExpressionEvaluatorFactory implements org.jxls.expression.ExpressionEvaluatorFactory { + + private final JexlPermissions permissions = new JexlPermissions() { + @Override + public boolean allow(Package pack) { + return true; + } + + @Override + public boolean allow(Class<?> clazz) { + return true; + } + + @Override + public boolean allow(Constructor<?> ctor) { + return true; + } + + @Override + public boolean allow(Method method) { + return true; + } + + @Override + public boolean allow(Field field) { + return true; + } + + @Override + public JexlPermissions compose(String... src) { + return this; + } + }; + + @Override + public ExpressionEvaluator createExpressionEvaluator(String expression) { + JexlExpressionEvaluator expressionEvaluator = expression == null + ? new JexlExpressionEvaluator() + : new JexlExpressionEvaluator(expression); + expressionEvaluator.setJexlEngine(new JexlBuilder() + .silent(true) + .strict(false) + .permissions(permissions) + .create()); + return expressionEvaluator; + } +} diff --git a/src/main/java/org/traccar/reports/common/ReportExecutor.java b/src/main/java/org/traccar/reports/common/ReportExecutor.java new file mode 100644 index 000000000..aed4b8c23 --- /dev/null +++ b/src/main/java/org/traccar/reports/common/ReportExecutor.java @@ -0,0 +1,25 @@ +/* + * Copyright 2023 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. + * 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.traccar.storage.StorageException; + +import java.io.IOException; +import java.io.OutputStream; + +public interface ReportExecutor { + void execute(OutputStream stream) throws StorageException, IOException; +} diff --git a/src/main/java/org/traccar/reports/common/ReportMailer.java b/src/main/java/org/traccar/reports/common/ReportMailer.java new file mode 100644 index 000000000..9fb30fe9f --- /dev/null +++ b/src/main/java/org/traccar/reports/common/ReportMailer.java @@ -0,0 +1,65 @@ +/* + * Copyright 2023 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. + * 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.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.traccar.api.security.PermissionsService; +import org.traccar.mail.MailManager; +import org.traccar.model.User; +import org.traccar.storage.StorageException; + +import jakarta.activation.DataHandler; +import jakarta.inject.Inject; +import jakarta.mail.MessagingException; +import jakarta.mail.internet.MimeBodyPart; +import jakarta.mail.util.ByteArrayDataSource; +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +public class ReportMailer { + + private static final Logger LOGGER = LoggerFactory.getLogger(ReportMailer.class); + + private final PermissionsService permissionsService; + private final MailManager mailManager; + + @Inject + public ReportMailer(PermissionsService permissionsService, MailManager mailManager) { + this.permissionsService = permissionsService; + this.mailManager = mailManager; + } + + public void sendAsync(long userId, ReportExecutor executor) { + new Thread(() -> { + try { + var stream = new ByteArrayOutputStream(); + executor.execute(stream); + + MimeBodyPart attachment = new MimeBodyPart(); + attachment.setFileName("report.xlsx"); + attachment.setDataHandler(new DataHandler(new ByteArrayDataSource( + stream.toByteArray(), "application/octet-stream"))); + + User user = permissionsService.getUser(userId); + mailManager.sendMessage(user, false, "Report", "The report is in the attachment.", attachment); + } catch (StorageException | IOException | MessagingException e) { + LOGGER.warn("Email report failed", e); + } + }).start(); + } + +} 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..43db82708 --- /dev/null +++ b/src/main/java/org/traccar/reports/common/ReportUtils.java @@ -0,0 +1,406 @@ +/* + * 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.app.VelocityEngine; +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.api.security.PermissionsService; +import org.traccar.config.Config; +import org.traccar.config.Keys; +import org.traccar.geocoder.Geocoder; +import org.traccar.helper.UnitsConverter; +import org.traccar.helper.model.AttributeUtil; +import org.traccar.helper.model.PositionUtil; +import org.traccar.helper.model.UserUtil; +import org.traccar.model.BaseModel; +import org.traccar.model.Device; +import org.traccar.model.Driver; +import org.traccar.model.Event; +import org.traccar.model.Position; +import org.traccar.model.User; +import org.traccar.reports.model.BaseReportItem; +import org.traccar.reports.model.StopReportItem; +import org.traccar.reports.model.TripReportItem; +import org.traccar.session.state.MotionProcessor; +import org.traccar.session.state.MotionState; +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 jakarta.annotation.Nullable; +import jakarta.inject.Inject; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.time.Duration; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Locale; +import java.util.Set; +import java.util.stream.Collectors; + +public class ReportUtils { + + private final Config config; + private final Storage storage; + private final PermissionsService permissionsService; + private final VelocityEngine velocityEngine; + private final Geocoder geocoder; + + @Inject + public ReportUtils( + Config config, Storage storage, PermissionsService permissionsService, + VelocityEngine velocityEngine, @Nullable Geocoder geocoder) { + this.config = config; + this.storage = storage; + this.permissionsService = permissionsService; + this.velocityEngine = velocityEngine; + this.geocoder = geocoder; + } + + public <T extends BaseModel> T getObject( + long userId, Class<T> clazz, long objectId) throws StorageException, SecurityException { + return storage.getObject(clazz, new Request( + new Columns.All(), + new Condition.And( + new Condition.Equals("id", objectId), + new Condition.Permission(User.class, userId, clazz)))); + } + + public void checkPeriodLimit(Date from, Date to) { + long limit = config.getLong(Keys.REPORT_PERIOD_LIMIT) * 1000; + if (limit > 0 && to.getTime() - from.getTime() > limit) { + throw new IllegalArgumentException("Time period exceeds the limit"); + } + } + + public 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 String findDriver(Position firstPosition, Position lastPosition) { + if (firstPosition.hasAttribute(Position.KEY_DRIVER_UNIQUE_ID)) { + return firstPosition.getString(Position.KEY_DRIVER_UNIQUE_ID); + } else if (lastPosition.hasAttribute(Position.KEY_DRIVER_UNIQUE_ID)) { + return lastPosition.getString(Position.KEY_DRIVER_UNIQUE_ID); + } + return null; + } + + public String findDriverName(String driverUniqueId) throws StorageException { + if (driverUniqueId != null) { + Driver driver = storage.getObject(Driver.class, new Request( + new Columns.All(), + new Condition.Equals("uniqueId", driverUniqueId))); + if (driver != null) { + return driver.getName(); + } + } + return null; + } + + public org.jxls.common.Context initializeContext(long userId) throws StorageException { + var server = permissionsService.getServer(); + var user = permissionsService.getUser(userId); + var context = PoiTransformer.createInitialContext(); + context.putVar("distanceUnit", UserUtil.getDistanceUnit(server, user)); + context.putVar("speedUnit", UserUtil.getSpeedUnit(server, user)); + context.putVar("volumeUnit", UserUtil.getVolumeUnit(server, user)); + context.putVar("webUrl", velocityEngine.getProperty("web.url")); + context.putVar("dateTool", new DateTool()); + context.putVar("numberTool", new NumberTool()); + context.putVar("timezone", UserUtil.getTimezone(server, user)); + context.putVar("locale", Locale.getDefault()); + context.putVar("bracketsRegex", "[\\{\\}\"]"); + return context; + } + + public void processTemplateWithSheets( + InputStream templateStream, OutputStream targetStream, org.jxls.common.Context context) throws IOException { + + Transformer transformer = TransformerFactory.createTransformer(templateStream, targetStream); + List<Area> xlsAreas = new XlsCommentAreaBuilder(transformer).build(); + for (Area xlsArea : xlsAreas) { + xlsArea.applyAt(new CellRef(xlsArea.getStartCellRef().getCellName()), context); + xlsArea.setFormulaProcessor(new StandardFormulaProcessor()); + xlsArea.processFormulas(); + } + transformer.deleteSheet(xlsAreas.get(0).getStartCellRef().getSheetName()); + transformer.write(); + } + + private TripReportItem calculateTrip( + Device device, Position startTrip, Position endTrip, double maxSpeed, + boolean ignoreOdometer) throws StorageException { + + TripReportItem trip = new TripReportItem(); + + long tripDuration = endTrip.getFixTime().getTime() - startTrip.getFixTime().getTime(); + long deviceId = startTrip.getDeviceId(); + trip.setDeviceId(deviceId); + trip.setDeviceName(device.getName()); + + trip.setStartPositionId(startTrip.getId()); + trip.setStartLat(startTrip.getLatitude()); + trip.setStartLon(startTrip.getLongitude()); + trip.setStartTime(startTrip.getFixTime()); + String startAddress = startTrip.getAddress(); + if (startAddress == null && geocoder != null && config.getBoolean(Keys.GEOCODER_ON_REQUEST)) { + startAddress = geocoder.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 && geocoder != null && config.getBoolean(Keys.GEOCODER_ON_REQUEST)) { + endAddress = geocoder.getAddress(endTrip.getLatitude(), endTrip.getLongitude(), null); + } + trip.setEndAddress(endAddress); + + trip.setDistance(PositionUtil.calculateDistance(startTrip, endTrip, !ignoreOdometer)); + trip.setDuration(tripDuration); + if (tripDuration > 0) { + trip.setAverageSpeed(UnitsConverter.knotsFromMps(trip.getDistance() * 1000 / tripDuration)); + } + trip.setMaxSpeed(maxSpeed); + 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 StopReportItem calculateStop( + Device device, Position startStop, Position endStop, boolean ignoreOdometer) { + + StopReportItem stop = new StopReportItem(); + + long deviceId = startStop.getDeviceId(); + stop.setDeviceId(deviceId); + stop.setDeviceName(device.getName()); + + stop.setPositionId(startStop.getId()); + stop.setLatitude(startStop.getLatitude()); + stop.setLongitude(startStop.getLongitude()); + stop.setStartTime(startStop.getFixTime()); + String address = startStop.getAddress(); + if (address == null && geocoder != null && config.getBoolean(Keys.GEOCODER_ON_REQUEST)) { + address = geocoder.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.hasAttribute(Position.KEY_HOURS) && endStop.hasAttribute(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; + + } + + @SuppressWarnings("unchecked") + private <T extends BaseReportItem> T calculateTripOrStop( + Device device, Position startPosition, Position endPosition, double maxSpeed, + boolean ignoreOdometer, Class<T> reportClass) throws StorageException { + + if (reportClass.equals(TripReportItem.class)) { + return (T) calculateTrip(device, startPosition, endPosition, maxSpeed, ignoreOdometer); + } else { + return (T) calculateStop(device, startPosition, endPosition, ignoreOdometer); + } + } + + private boolean isMoving(List<Position> 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; + } + } + return positions.get(index).getBoolean(Position.KEY_MOTION); + } + + public <T extends BaseReportItem> List<T> detectTripsAndStops( + Device device, Date from, Date to, Class<T> reportClass) throws StorageException { + + long threshold = config.getLong(Keys.REPORT_FAST_THRESHOLD); + if (Duration.between(from.toInstant(), to.toInstant()).toSeconds() > threshold) { + return fastTripsAndStops(device, from, to, reportClass); + } else { + return slowTripsAndStops(device, from, to, reportClass); + } + } + + public <T extends BaseReportItem> List<T> slowTripsAndStops( + Device device, Date from, Date to, Class<T> reportClass) throws StorageException { + + List<T> result = new ArrayList<>(); + TripsConfig tripsConfig = new TripsConfig( + new AttributeUtil.StorageProvider(config, storage, permissionsService, device)); + boolean ignoreOdometer = config.getBoolean(Keys.REPORT_IGNORE_ODOMETER); + + var positions = PositionUtil.getPositions(storage, device.getId(), from, to); + if (!positions.isEmpty()) { + boolean trips = reportClass.equals(TripReportItem.class); + + MotionState motionState = new MotionState(); + boolean initialValue = isMoving(positions, 0, tripsConfig); + motionState.setMotionStreak(initialValue); + motionState.setMotionState(initialValue); + + boolean detected = trips == motionState.getMotionState(); + double maxSpeed = 0; + int startEventIndex = detected ? 0 : -1; + int startNoEventIndex = -1; + for (int i = 0; i < positions.size(); i++) { + boolean motion = isMoving(positions, i, tripsConfig); + if (motionState.getMotionState() != motion) { + if (motion == trips) { + if (!detected) { + startEventIndex = i; + maxSpeed = positions.get(i).getSpeed(); + } + startNoEventIndex = -1; + } else { + startNoEventIndex = i; + } + } else { + maxSpeed = Math.max(maxSpeed, positions.get(i).getSpeed()); + } + + MotionProcessor.updateState(motionState, positions.get(i), motion, tripsConfig); + if (motionState.getEvent() != null) { + if (motion == trips) { + detected = true; + startNoEventIndex = -1; + } else if (startEventIndex >= 0 && startNoEventIndex >= 0) { + result.add(calculateTripOrStop( + device, positions.get(startEventIndex), positions.get(startNoEventIndex), + maxSpeed, ignoreOdometer, reportClass)); + detected = false; + startEventIndex = -1; + startNoEventIndex = -1; + } + } + } + if (detected & startEventIndex >= 0 && startEventIndex < positions.size() - 1) { + int endIndex = startNoEventIndex >= 0 ? startNoEventIndex : positions.size() - 1; + result.add(calculateTripOrStop( + device, positions.get(startEventIndex), positions.get(endIndex), + maxSpeed, ignoreOdometer, reportClass)); + } + } + + return result; + } + + public <T extends BaseReportItem> List<T> fastTripsAndStops( + Device device, Date from, Date to, Class<T> reportClass) throws StorageException { + + List<T> result = new ArrayList<>(); + boolean ignoreOdometer = config.getBoolean(Keys.REPORT_IGNORE_ODOMETER); + boolean trips = reportClass.equals(TripReportItem.class); + Set<String> filter = Set.of(Event.TYPE_DEVICE_MOVING, Event.TYPE_DEVICE_STOPPED); + + var events = storage.getObjects(Event.class, new Request( + new Columns.All(), + new Condition.And( + new Condition.Equals("deviceId", device.getId()), + new Condition.Between("eventTime", "from", from, "to", to)), + new Order("eventTime"))); + var filteredEvents = events.stream() + .filter(event -> filter.contains(event.getType())) + .collect(Collectors.toList()); + + Event startEvent = null; + for (Event event : filteredEvents) { + boolean motion = event.getType().equals(Event.TYPE_DEVICE_MOVING); + if (motion == trips) { + startEvent = event; + } else if (startEvent != null) { + Position startPosition = storage.getObject(Position.class, new Request( + new Columns.All(), new Condition.Equals("id", startEvent.getPositionId()))); + Position endPosition = storage.getObject(Position.class, new Request( + new Columns.All(), new Condition.Equals("id", event.getPositionId()))); + if (startPosition != null && endPosition != null) { + result.add(calculateTripOrStop( + device, startPosition, endPosition, 0, ignoreOdometer, reportClass)); + } + startEvent = null; + } + } + + 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..2792114d4 --- /dev/null +++ b/src/main/java/org/traccar/reports/common/TripsConfig.java @@ -0,0 +1,73 @@ +/* + * Copyright 2017 - 2023 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; + +import org.traccar.config.Keys; +import org.traccar.helper.model.AttributeUtil; + +public class TripsConfig { + + public TripsConfig(AttributeUtil.Provider attributeProvider) { + this( + AttributeUtil.lookup(attributeProvider, Keys.REPORT_TRIP_MINIMAL_TRIP_DISTANCE), + AttributeUtil.lookup(attributeProvider, Keys.REPORT_TRIP_MINIMAL_TRIP_DURATION) * 1000, + AttributeUtil.lookup(attributeProvider, Keys.REPORT_TRIP_MINIMAL_PARKING_DURATION) * 1000, + AttributeUtil.lookup(attributeProvider, Keys.REPORT_TRIP_MINIMAL_NO_DATA_DURATION) * 1000, + AttributeUtil.lookup(attributeProvider, Keys.REPORT_TRIP_USE_IGNITION)); + } + + public TripsConfig( + double minimalTripDistance, long minimalTripDuration, long minimalParkingDuration, + long minimalNoDataDuration, boolean useIgnition) { + this.minimalTripDistance = minimalTripDistance; + this.minimalTripDuration = minimalTripDuration; + this.minimalParkingDuration = minimalParkingDuration; + this.minimalNoDataDuration = minimalNoDataDuration; + this.useIgnition = useIgnition; + } + + 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; + } + +} diff --git a/src/main/java/org/traccar/reports/model/BaseReport.java b/src/main/java/org/traccar/reports/model/BaseReportItem.java index 928c0557d..6e270dfe3 100644 --- a/src/main/java/org/traccar/reports/model/BaseReport.java +++ b/src/main/java/org/traccar/reports/model/BaseReportItem.java @@ -18,7 +18,7 @@ package org.traccar.reports.model; import java.util.Date; -public class BaseReport { +public class BaseReportItem { private long deviceId; diff --git a/src/main/java/org/traccar/reports/model/CombinedReportItem.java b/src/main/java/org/traccar/reports/model/CombinedReportItem.java new file mode 100644 index 000000000..810e00916 --- /dev/null +++ b/src/main/java/org/traccar/reports/model/CombinedReportItem.java @@ -0,0 +1,65 @@ +/* + * Copyright 2023 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. + * 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 org.traccar.model.Event; +import org.traccar.model.Position; + +import java.util.List; + +public class CombinedReportItem { + + private long deviceId; + + public long getDeviceId() { + return deviceId; + } + + public void setDeviceId(long deviceId) { + this.deviceId = deviceId; + } + + private List<double[]> route; + + public List<double[]> getRoute() { + return route; + } + + public void setRoute(List<double[]> route) { + this.route = route; + } + + private List<Event> events; + + public List<Event> getEvents() { + return events; + } + + public void setEvents(List<Event> events) { + this.events = events; + } + + private List<Position> positions; + + public List<Position> getPositions() { + return positions; + } + + public void setPositions(List<Position> positions) { + this.positions = positions; + } + +} diff --git a/src/main/java/org/traccar/reports/model/DeviceReport.java b/src/main/java/org/traccar/reports/model/DeviceReportSection.java index 932753d15..ffc4d774f 100644 --- a/src/main/java/org/traccar/reports/model/DeviceReport.java +++ b/src/main/java/org/traccar/reports/model/DeviceReportSection.java @@ -20,7 +20,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; -public class DeviceReport { +public class DeviceReportSection { private String deviceName; diff --git a/src/main/java/org/traccar/reports/model/StopReport.java b/src/main/java/org/traccar/reports/model/StopReportItem.java index e20f2c503..3c35bdc21 100644 --- a/src/main/java/org/traccar/reports/model/StopReport.java +++ b/src/main/java/org/traccar/reports/model/StopReportItem.java @@ -16,7 +16,7 @@ */ package org.traccar.reports.model; -public class StopReport extends BaseReport { +public class StopReportItem extends BaseReportItem { private long positionId; diff --git a/src/main/java/org/traccar/reports/model/SummaryReport.java b/src/main/java/org/traccar/reports/model/SummaryReportItem.java index 886f8b9e2..44a15cf1d 100644 --- a/src/main/java/org/traccar/reports/model/SummaryReport.java +++ b/src/main/java/org/traccar/reports/model/SummaryReportItem.java @@ -16,7 +16,7 @@ */ package org.traccar.reports.model; -public class SummaryReport extends BaseReport { +public class SummaryReportItem extends BaseReportItem { private long engineHours; // milliseconds diff --git a/src/main/java/org/traccar/reports/model/TripReport.java b/src/main/java/org/traccar/reports/model/TripReportItem.java index 151c34bd5..332a34cca 100644 --- a/src/main/java/org/traccar/reports/model/TripReport.java +++ b/src/main/java/org/traccar/reports/model/TripReportItem.java @@ -16,7 +16,7 @@ */ package org.traccar.reports.model; -public class TripReport extends BaseReport { +public class TripReportItem extends BaseReportItem { private long startPositionId; 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 0f0c615d3..000000000 --- a/src/main/java/org/traccar/reports/model/TripsConfig.java +++ /dev/null @@ -1,105 +0,0 @@ -/* - * 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, - 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 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 long minimalNoDataDuration; - - public long getMinimalNoDataDuration() { - return minimalNoDataDuration; - } - - public void setMinimalNoDataDuration(long minimalNoDataDuration) { - this.minimalNoDataDuration = minimalNoDataDuration; - } - - private boolean useIgnition; - - public boolean getUseIgnition() { - return useIgnition; - } - - public void setUseIgnition(boolean useIgnition) { - this.useIgnition = useIgnition; - } - - private boolean processInvalidPositions; - - public boolean getProcessInvalidPositions() { - return processInvalidPositions; - } - - public void setProcessInvalidPositions(boolean processInvalidPositions) { - this.processInvalidPositions = processInvalidPositions; - } - - private double speedThreshold; - - public double getSpeedThreshold() { - return speedThreshold; - } - - public void setSpeedThreshold(double speedThreshold) { - this.speedThreshold = speedThreshold; - } - -} |