package com.grigowashere.aismap.maps; import android.graphics.Color; import android.util.Log; import com.yandex.mapkit.geometry.Point; import com.yandex.mapkit.map.MapObjectCollection; import com.yandex.mapkit.map.PolylineMapObject; import com.yandex.mapkit.map.MapObject; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ConcurrentLinkedQueue; /** * Класс для отслеживания и отображения пути движения судна * Отображает сплошную линию пройденного пути и прогнозируемое движение */ public class VesselPathTracker { private static final String TAG = "VesselPathTracker"; private static final int MAX_PATH_POINTS = 100; // Максимальное количество точек в пути private static final long MIN_TIME_BETWEEN_POINTS = 1000; // Минимальное время между точками (1 секунда) private static final double MIN_DISTANCE_BETWEEN_POINTS = 10.0; // Минимальное расстояние между точками (10 метров) private String vesselId; private MapObjectCollection mapObjects; private ConcurrentLinkedQueue pathHistory; private PolylineMapObject pathLine; private PolylineMapObject predictionLine; private long lastUpdateTime; private Point lastPosition; // Настройки отображения private int pathColor = Color.CYAN; private int predictionColor = Color.YELLOW; private float pathWidth = 3.0f; private float predictionWidth = 2.0f; private boolean isEnabled = true; /** * Точка пути с временной меткой */ private static class PathPoint { public final Point position; public final long timestamp; public final double speed; public final double course; public PathPoint(Point position, long timestamp, double speed, double course) { this.position = position; this.timestamp = timestamp; this.speed = speed; this.course = course; } } public VesselPathTracker(String vesselId, MapObjectCollection mapObjects) { this.vesselId = vesselId; this.mapObjects = mapObjects; this.pathHistory = new ConcurrentLinkedQueue<>(); this.lastUpdateTime = 0; Log.d(TAG, "Created VesselPathTracker for vessel: " + vesselId + ", mapObjects: " + (mapObjects != null ? "not null" : "null")); } /** * Обновляет путь судна новой позицией */ public void updatePosition(double latitude, double longitude, double speed, double course) { if (!isEnabled) { Log.d(TAG, "VesselPathTracker disabled for vessel: " + vesselId); return; } long currentTime = System.currentTimeMillis(); Point newPosition = new Point(latitude, longitude); Log.d(TAG, "updatePosition called for vessel: " + vesselId + ", lat: " + latitude + ", lon: " + longitude + ", speed: " + speed + ", course: " + course); // Проверяем, нужно ли добавить новую точку if (shouldAddPoint(newPosition, currentTime)) { PathPoint newPoint = new PathPoint(newPosition, currentTime, speed, course); pathHistory.offer(newPoint); // Ограничиваем количество точек while (pathHistory.size() > MAX_PATH_POINTS) { pathHistory.poll(); } lastPosition = newPosition; lastUpdateTime = currentTime; Log.d(TAG, "Added new point to path for vessel: " + vesselId + ", total points: " + pathHistory.size()); // Обновляем отображение пути updatePathDisplay(); } else { Log.d(TAG, "Point not added for vessel: " + vesselId + " (time or distance filter)"); } } /** * Проверяет, нужно ли добавить новую точку в путь */ private boolean shouldAddPoint(Point newPosition, long currentTime) { // Проверяем время if (currentTime - lastUpdateTime < MIN_TIME_BETWEEN_POINTS) { return false; } // Проверяем расстояние if (lastPosition != null) { double distance = calculateDistance(lastPosition, newPosition); if (distance < MIN_DISTANCE_BETWEEN_POINTS) { return false; } } return true; } /** * Обновляет отображение пути на карте */ private void updatePathDisplay() { Log.d(TAG, "updatePathDisplay called for vessel: " + vesselId); if (pathHistory.isEmpty()) { Log.d(TAG, "Path history is empty for vessel: " + vesselId); return; } if (mapObjects == null) { Log.d(TAG, "MapObjects is null for vessel: " + vesselId); return; } // Создаем список точек для пройденного пути List pathPoints = new ArrayList<>(); for (PathPoint point : pathHistory) { pathPoints.add(point.position); } Log.d(TAG, "Creating path line with " + pathPoints.size() + " points for vessel: " + vesselId); // Удаляем старые линии try { if (pathLine != null) { Log.d(TAG, "Removing old path line for vessel: " + vesselId); mapObjects.remove(pathLine); pathLine = null; } if (predictionLine != null) { Log.d(TAG, "Removing old prediction line for vessel: " + vesselId); mapObjects.remove(predictionLine); predictionLine = null; } } catch (RuntimeException e) { Log.e(TAG, "Error removing old lines for vessel: " + vesselId, e); // Коллекция могла быть инвалидирована (weak_ptr expired). Прекращаем обновления. isEnabled = false; return; } // Создаем линию пройденного пути if (pathPoints.size() > 1) { try { Log.d(TAG, "Adding new path line for vessel: " + vesselId); pathLine = mapObjects.addPolyline(new com.yandex.mapkit.geometry.Polyline(pathPoints)); if (pathLine != null) { pathLine.setStrokeColor(pathColor); pathLine.setStrokeWidth(pathWidth); Log.d(TAG, "Path line created successfully for vessel: " + vesselId + ", color: " + Integer.toHexString(pathColor) + ", width: " + pathWidth); } else { Log.e(TAG, "Failed to create path line for vessel: " + vesselId); } } catch (RuntimeException e) { Log.e(TAG, "Error creating path line for vessel: " + vesselId, e); isEnabled = false; return; } } else { Log.d(TAG, "Not enough points for path line for vessel: " + vesselId + " (need at least 2, have " + pathPoints.size() + ")"); } // Создаем линию прогнозируемого движения createPredictionLine(); } /** * Создает линию прогнозируемого движения */ private void createPredictionLine() { Log.d(TAG, "createPredictionLine called for vessel: " + vesselId); if (pathHistory.isEmpty()) { Log.d(TAG, "Path history is empty for prediction line for vessel: " + vesselId); return; } if (mapObjects == null) { Log.d(TAG, "MapObjects is null for prediction line for vessel: " + vesselId); return; } // Получаем последнюю точку PathPoint lastPoint = null; for (PathPoint point : pathHistory) { lastPoint = point; } if (lastPoint == null || lastPoint.speed <= 0) { Log.d(TAG, "Cannot create prediction line for vessel: " + vesselId + " (lastPoint: " + (lastPoint != null ? "not null" : "null") + ", speed: " + (lastPoint != null ? lastPoint.speed : "N/A") + ")"); return; } // Рассчитываем прогнозируемую позицию через 1 минуту double predictionTimeMinutes = 1.0; // 1 минута double predictionDistance = lastPoint.speed * predictionTimeMinutes * 60.0; // расстояние в метрах Log.d(TAG, "Creating prediction line for vessel: " + vesselId + ", speed: " + lastPoint.speed + ", course: " + lastPoint.course + ", prediction distance: " + predictionDistance + "m"); // Конвертируем курс в радианы double courseRad = Math.toRadians(lastPoint.course); // Рассчитываем новую позицию double earthRadius = 6371000; // радиус Земли в метрах double lat1 = Math.toRadians(lastPoint.position.getLatitude()); double lon1 = Math.toRadians(lastPoint.position.getLongitude()); double lat2 = Math.asin(Math.sin(lat1) * Math.cos(predictionDistance / earthRadius) + Math.cos(lat1) * Math.sin(predictionDistance / earthRadius) * Math.cos(courseRad)); double lon2 = lon1 + Math.atan2(Math.sin(courseRad) * Math.sin(predictionDistance / earthRadius) * Math.cos(lat1), Math.cos(predictionDistance / earthRadius) - Math.sin(lat1) * Math.sin(lat2)); Point predictionPoint = new Point(Math.toDegrees(lat2), Math.toDegrees(lon2)); // Создаем линию прогноза List predictionPoints = new ArrayList<>(); predictionPoints.add(lastPoint.position); predictionPoints.add(predictionPoint); try { Log.d(TAG, "Adding prediction line for vessel: " + vesselId); predictionLine = mapObjects.addPolyline(new com.yandex.mapkit.geometry.Polyline(predictionPoints)); if (predictionLine != null) { predictionLine.setStrokeColor(predictionColor); predictionLine.setStrokeWidth(predictionWidth); Log.d(TAG, "Prediction line created successfully for vessel: " + vesselId + ", color: " + Integer.toHexString(predictionColor) + ", width: " + predictionWidth); } else { Log.e(TAG, "Failed to create prediction line for vessel: " + vesselId); } } catch (RuntimeException e) { Log.e(TAG, "Error creating prediction line for vessel: " + vesselId, e); isEnabled = false; } } /** * Рассчитывает расстояние между двумя точками в метрах */ private double calculateDistance(Point point1, Point point2) { double lat1 = Math.toRadians(point1.getLatitude()); double lon1 = Math.toRadians(point1.getLongitude()); double lat2 = Math.toRadians(point2.getLatitude()); double lon2 = Math.toRadians(point2.getLongitude()); double dlat = lat2 - lat1; double dlon = lon2 - lon1; double a = Math.sin(dlat / 2) * Math.sin(dlat / 2) + Math.cos(lat1) * Math.cos(lat2) * Math.sin(dlon / 2) * Math.sin(dlon / 2); double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); return 6371000 * c; // радиус Земли в метрах } /** * Очищает путь судна */ public void clearPath() { try { if (pathLine != null && mapObjects != null) { mapObjects.remove(pathLine); pathLine = null; } if (predictionLine != null && mapObjects != null) { mapObjects.remove(predictionLine); predictionLine = null; } } catch (RuntimeException ignored) { // Игнорируем ошибки очистки при невалидной коллекции } pathHistory.clear(); lastPosition = null; } /** * Удаляет трекер пути */ public void remove() { clearPath(); } /** * Включает/выключает отображение пути */ public void setEnabled(boolean enabled) { this.isEnabled = enabled; if (!enabled) { clearPath(); } } /** * Устанавливает цвет пройденного пути */ public void setPathColor(int color) { this.pathColor = color; if (pathLine != null) { pathLine.setStrokeColor(color); } } /** * Устанавливает цвет прогнозируемого пути */ public void setPredictionColor(int color) { this.predictionColor = color; if (predictionLine != null) { predictionLine.setStrokeColor(color); } } /** * Устанавливает ширину линий */ public void setLineWidth(float pathWidth, float predictionWidth) { this.pathWidth = pathWidth; this.predictionWidth = predictionWidth; if (pathLine != null) { pathLine.setStrokeWidth(pathWidth); } if (predictionLine != null) { predictionLine.setStrokeWidth(predictionWidth); } } /** * Проверяет, активен ли трекер */ public boolean isActive() { return isEnabled && !pathHistory.isEmpty(); } /** * Получает количество точек в пути */ public int getPathPointCount() { return pathHistory.size(); } }