Подготовка к крупным изменениям: карта, AIS и UI

- Яндекс/MapForge: правки в менеджерах и обёртках маркеров (улучшена отрисовка/логика)
- NMEAParser: корректировки парсинга и стабильности
- Модель AISVessel: уточнение полей/логики
- Настройки: правки в SettingsActivity и SettingsManager, актуализация AppController
- UI: обновлены activity_main, activity_settings, bottom_sheet_ais_vessel; меню main_menu
- Ресурсы: добавлен drawable/targetclassa.xml, обновлён drawable/target.xml
- Конфигурация: правки AndroidManifest и app/build.gradle
- Прочее: изменения в .idea (не влияют на сборку)
This commit is contained in:
2025-09-23 11:53:23 +03:00
parent a2f1775f9f
commit 41432665ea
37 changed files with 6561 additions and 161 deletions
@@ -0,0 +1,329 @@
package com.grigowashere.aismap.maps;
import android.graphics.Color;
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 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<PathPoint> 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;
}
/**
* Обновляет путь судна новой позицией
*/
public void updatePosition(double latitude, double longitude, double speed, double course) {
if (!isEnabled) {
return;
}
long currentTime = System.currentTimeMillis();
Point newPosition = new Point(latitude, longitude);
// Проверяем, нужно ли добавить новую точку
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;
// Обновляем отображение пути
updatePathDisplay();
}
}
/**
* Проверяет, нужно ли добавить новую точку в путь
*/
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() {
if (pathHistory.isEmpty()) {
return;
}
if (mapObjects == null) {
return;
}
// Создаем список точек для пройденного пути
List<Point> pathPoints = new ArrayList<>();
for (PathPoint point : pathHistory) {
pathPoints.add(point.position);
}
// Удаляем старые линии
try {
if (pathLine != null) {
mapObjects.remove(pathLine);
pathLine = null;
}
if (predictionLine != null) {
mapObjects.remove(predictionLine);
predictionLine = null;
}
} catch (RuntimeException ignored) {
// Коллекция могла быть инвалидирована (weak_ptr expired). Прекращаем обновления.
isEnabled = false;
return;
}
// Создаем линию пройденного пути
if (pathPoints.size() > 1) {
try {
pathLine = mapObjects.addPolyline(new com.yandex.mapkit.geometry.Polyline(pathPoints));
if (pathLine != null) {
pathLine.setStrokeColor(pathColor);
pathLine.setStrokeWidth(pathWidth);
}
} catch (RuntimeException ignored) {
isEnabled = false;
return;
}
}
// Создаем линию прогнозируемого движения
createPredictionLine();
}
/**
* Создает линию прогнозируемого движения
*/
private void createPredictionLine() {
if (pathHistory.isEmpty()) {
return;
}
if (mapObjects == null) {
return;
}
// Получаем последнюю точку
PathPoint lastPoint = null;
for (PathPoint point : pathHistory) {
lastPoint = point;
}
if (lastPoint == null || lastPoint.speed <= 0) {
return;
}
// Рассчитываем прогнозируемую позицию через 1 минуту
double predictionTimeMinutes = 1.0; // 1 минута
double predictionDistance = lastPoint.speed * predictionTimeMinutes * 60.0; // расстояние в метрах
// Конвертируем курс в радианы
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<Point> predictionPoints = new ArrayList<>();
predictionPoints.add(lastPoint.position);
predictionPoints.add(predictionPoint);
try {
predictionLine = mapObjects.addPolyline(new com.yandex.mapkit.geometry.Polyline(predictionPoints));
if (predictionLine != null) {
predictionLine.setStrokeColor(predictionColor);
predictionLine.setStrokeWidth(predictionWidth);
// Сплошная линия для прогноза (по умолчанию)
}
} catch (RuntimeException ignored) {
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();
}
}