feat: новая архитектура UI и расширенная визуализация AIS

Архитектурные улучшения:
- Внедрен UIRenderingCoordinator с централизованным throttling
- Решены проблемы зависания UI через батчинг операций карты
- Добавлен VesselPathController для отслеживания маршрутов
- Реализован MapLibreMapImpl как альтернатива Яндекс.Картам

Визуализация AIS:
- Добавлены векторные иконки для всех типов судов
- Разделение Class A/B судов с соответствующими иконками
- Иконки навигационных статусов (anchor, moored, engine, sail)
- Улучшенный CursorOverlay с информацией о судах

Производительность:
- Throttling UI обновлений (vessel: 500ms, AIS: 1s, paths: 2s)
- Устранение утечек Handler объектов
- Оптимизация GeoJSON операций в MapLibre
This commit is contained in:
2025-10-02 09:15:33 +03:00
parent 41432665ea
commit b5aee265bc
85 changed files with 7132 additions and 449 deletions
@@ -8,8 +8,12 @@ import com.grigowashere.aismap.maps.MapInterface;
import com.grigowashere.aismap.data.Repository;
import com.grigowashere.aismap.data.mapper.AISVesselMapper;
import com.grigowashere.aismap.services.NotificationService;
import com.grigowashere.aismap.utils.SettingsManager;
import com.grigowashere.aismap.ui.UIDataChangeNotifier;
import java.util.List;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@@ -39,6 +43,10 @@ public class AppController implements
private ExecutorService executor;
private com.grigowashere.aismap.data.Repository repository;
private NotificationService notificationService;
private SettingsManager settingsManager;
private VesselPathController pathController;
// VesselPathController для каждого AIS судна (ключ: MMSI)
private final Map<String, VesselPathController> aisPathControllers = new HashMap<>();
private boolean isUDPEnabled;
private boolean isAndroidNMEAEnabled;
@@ -56,9 +64,18 @@ public class AppController implements
private Runnable dbCleanupRunnable;
private static final long DB_CLEANUP_INTERVAL = 60000; // 1 минута
// Callback для обновления UI
// Единый Handler для всех UI операций (предотвращение утечек Handler'ов)
private android.os.Handler uiHandler;
// Индикаторы UI данных для централизованного throttling
private UIDataChangeNotifier uiDataNotifier;
// Callback для обновления UI (legacy для MainActivity)
private UIUpdateCallback uiUpdateCallback;
// Диагностика сервисов
private long lastServiceLogTime = 0;
public interface UIUpdateCallback {
void onVesselPositionUpdated(Vessel vessel);
void onGPSQualityUpdated(Vessel vessel);
@@ -80,11 +97,16 @@ public class AppController implements
this.executor = Executors.newCachedThreadPool();
this.repository = new com.grigowashere.aismap.data.Repository(context);
this.notificationService = new NotificationService(context);
this.settingsManager = new SettingsManager(context);
this.pathController = new VesselPathController(context, settingsManager);
// Инициализируем Handler для периодической очистки БД
this.dbCleanupHandler = new android.os.Handler(android.os.Looper.getMainLooper());
this.dbCleanupRunnable = this::performDatabaseCleanup;
// Инициализируем единый UI Handler
this.uiHandler = new android.os.Handler(android.os.Looper.getMainLooper());
initializeControllers();
}
@@ -113,28 +135,54 @@ public class AppController implements
androidNmeaListener = new AndroidNMEAListener(context);
androidNmeaListener.setCallback(this);
// Восстанавливаем данные из БД при старте
try {
com.grigowashere.aismap.data.entity.VesselEntity latest = repository.getLatestOwnVesselSync();
if (latest != null) {
ownVessel.setLatitude(latest.latitude);
ownVessel.setLongitude(latest.longitude);
ownVessel.setAccuracy(latest.accuracy);
ownVessel.setFixTime(latest.fixTime);
}
java.util.List<com.grigowashere.aismap.data.entity.AISVesselEntity> list = repository.getAllAISSync();
if (list != null) {
for (com.grigowashere.aismap.data.entity.AISVesselEntity entity : list) {
// Используем маппер для полного восстановления всех полей
AISVessel vessel = AISVesselMapper.toModel(entity);
aisVessels.add(vessel);
Log.d(TAG, "AIS судно восстановлено из БД с полными данными: " + vessel.getMmsi());
// Восстанавливаем данные из БД при старте АСИНХРОННО
Log.i(TAG, "🔄 Запускаем асинхронное восстановление данных из БД...");
executor.execute(() -> {
try {
Log.d(TAG, "📊 Загружаем данные судна из БД...");
com.grigowashere.aismap.data.entity.VesselEntity latest = repository.getLatestOwnVesselSync();
if (latest != null) {
ownVessel.setLatitude(latest.latitude);
ownVessel.setLongitude(latest.longitude);
ownVessel.setAccuracy(latest.accuracy);
ownVessel.setFixTime(latest.fixTime);
Log.d(TAG, "✅ Данные судна восстановлены: " + latest.latitude + "," + latest.longitude);
} else {
Log.d(TAG, "ℹ️ Нет данных судна в БД");
}
Log.i(TAG, "Восстановлено " + list.size() + " AIS судов из БД с полными данными");
Log.d(TAG, "🚢 Загружаем AIS суда из БД...");
java.util.List<com.grigowashere.aismap.data.entity.AISVesselEntity> list = repository.getAllAISSync();
if (list != null && !list.isEmpty()) {
synchronized (aisVessels) {
aisVessels.clear(); // Очищаем перед восстановлением
for (com.grigowashere.aismap.data.entity.AISVesselEntity entity : list) {
// Используем маппер для полного восстановления всех полей
AISVessel vessel = AISVesselMapper.toModel(entity);
aisVessels.add(vessel);
Log.d(TAG, "AIS судно восстановлено из БД с полными данными: " + vessel.getMmsi());
}
}
Log.i(TAG, "✅ Восстановлено " + list.size() + " AIS судов из БД с полными данными");
} else {
Log.d(TAG, "ℹ️ Нет AIS судов в БД");
}
// Уведомляем UI о восстановлении данных (если mapInterface уже установлен)
uiHandler.post(() -> {
if (mapInterface != null) {
Log.i(TAG, "🔄 Уведомляем UI о восстановленных данных...");
// Восстановление маркеров будет выполнено через setMapInterface()
// когда он будет вызван из MainActivity
} else {
Log.d(TAG, "⏳ mapInterface еще не установлен, восстановление отложено");
}
});
} catch (Exception e) {
Log.e(TAG, "❌ Ошибка восстановления данных из БД: " + e.getMessage(), e);
}
} catch (Exception e) {
Log.e(TAG, "Ошибка восстановления данных из БД: " + e.getMessage(), e);
}
});
}
/**
@@ -148,28 +196,45 @@ public class AppController implements
mapInterface.setMarkerClickListener(this);
Log.i(TAG, "MarkerClickListener установлен, теперь можно создавать маркеры");
// Восстановление отрисовки сохранённых данных на карте
new android.os.Handler(android.os.Looper.getMainLooper()).post(() -> {
try {
// Позиция нашего судна
if (ownVessel != null && ownVessel.getLatitude() != 0 && ownVessel.getLongitude() != 0) {
mapInterface.updateOwnVesselPosition(ownVessel);
}
// AIS маркеры
if (aisVessels != null && !aisVessels.isEmpty()) {
for (AISVessel v : aisVessels) {
mapInterface.addAISVesselMarker(v);
}
}
} catch (Exception e) {
Log.e(TAG, "Ошибка восстановления отрисовки на карте: " + e.getMessage(), e);
// Уведомляем UI Coordinator о восстановлении данных
if (uiDataNotifier != null) {
Log.i(TAG, "🔄 Восстановление данных через UI Coordinator");
// Восстанавливаем позицию собственного судна
if (ownVessel != null && ownVessel.getLatitude() != 0 && ownVessel.getLongitude() != 0) {
Log.i(TAG, "📍 Восстанавливаем позицию судна: " + ownVessel.getLatitude() + "," + ownVessel.getLongitude());
uiDataNotifier.onVesselPositionChanged(ownVessel);
} else {
Log.w(TAG, "⚠️ Судно не имеет валидных координат для восстановления");
}
});
// Восстанавливаем AIS суда
if (aisVessels != null && !aisVessels.isEmpty()) {
Log.i(TAG, "🚢 Восстанавливаем " + aisVessels.size() + " AIS судов");
for (AISVessel v : aisVessels) {
Log.d(TAG, " - AIS судно: " + v.getMmsi() + " на " + v.getLatitude() + "," + v.getLongitude());
uiDataNotifier.onAISVesselChanged(v);
}
Log.i(TAG, "" + aisVessels.size() + " AIS судов отправлено в UI Coordinator");
} else {
Log.i(TAG, "ℹ️ Нет AIS судов для восстановления");
}
} else {
Log.w(TAG, "❌ uiDataNotifier не установлен при восстановлении данных - маркеры НЕ будут восстановлены!");
}
}
}
/**
* Устанавливает callback для обновления UI
* Устанавливает индикатор изменений данных для централизованного UI throttling
*/
public void setUIDataChangeNotifier(UIDataChangeNotifier notifier) {
this.uiDataNotifier = notifier;
Log.i(TAG, "UIDataChangeNotifier установлен: " + (notifier != null ? "success" : "null"));
}
/**
* Устанавливает callback для обновления UI (legacy для MainActivity)
*/
public void setUIUpdateCallback(UIUpdateCallback callback) {
this.uiUpdateCallback = callback;
@@ -344,6 +409,18 @@ public class AppController implements
ownVessel.setFixTime(vessel.getFixTime());
ownVessel.setFixQuality(vessel.getFixQuality());
// Добавляем точку в путь судна
if (pathController != null) {
boolean pointAdded = pathController.addPathPoint(
vessel.getLongitude(),
vessel.getLatitude(),
(float) ownVessel.getSpeed()
);
if (pointAdded) {
Log.d(TAG, "Точка пути добавлена из GPS: " + pathController.getPathPointsCount() + " точек");
}
}
// Сохраняем позицию в локальную БД
try {
com.grigowashere.aismap.data.entity.VesselEntity ve = new com.grigowashere.aismap.data.entity.VesselEntity();
@@ -361,19 +438,12 @@ public class AppController implements
uiUpdateCallback.onVesselPositionUpdated(ownVessel);
}
// Обновляем карту в главном потоке с throttling
if (mapInterface != null) {
Log.i(TAG, "Обновляем позицию на карте...");
// Используем postDelayed для предотвращения частых обновлений
new android.os.Handler(android.os.Looper.getMainLooper()).postDelayed(() -> {
try {
Log.i(TAG, "Вызываем mapInterface.updateOwnVesselPosition...");
mapInterface.updateOwnVesselPosition(ownVessel);
Log.i(TAG, "Позиция на карте обновлена");
} catch (Exception e) {
Log.e(TAG, "Ошибка обновления позиции на карте: " + e.getMessage(), e);
}
}, 100); // Задержка 100мс для throttling
// Уведомляем UI Coordinator об изменении позиции судна (централизованный throttling)
if (uiDataNotifier != null) {
Log.d(TAG, "Уведомляем UI Coordinator об изменении позиции судна");
uiDataNotifier.onVesselPositionChanged(ownVessel);
} else {
Log.w(TAG, "uiDataNotifier не установлен, пропускаем UI обновление");
}
}
@@ -386,17 +456,23 @@ public class AppController implements
@Override
public void onVesselUpdated(Vessel vessel) {
Log.i(TAG, "🔄 onVesselUpdated вызван: lat=" + vessel.getLatitude() +
", lon=" + vessel.getLongitude() +
", course=" + vessel.getCourse() +
", speed=" + vessel.getSpeed());
// Сокращаем шум логов: подробности обновления судна убраны
// Обновляем координаты, если они есть (для режима "только NMEA")
if (vessel.getLatitude() != 0 && vessel.getLongitude() != 0) {
ownVessel.setLatitude(vessel.getLatitude());
ownVessel.setLongitude(vessel.getLongitude());
Log.i(TAG, "📍 Координаты обновлены из NMEA: lat=" + vessel.getLatitude() +
", lon=" + vessel.getLongitude());
// Сокращаем шум логов: координаты обновлены (без детализации)
// Добавляем точку в путь судна
if (pathController != null) {
boolean pointAdded = pathController.addPathPoint(
vessel.getLongitude(),
vessel.getLatitude(),
(float) vessel.getSpeed()
);
// Убираем лог о добавлении каждой точки пути
}
}
// Обновляем дополнительные данные
@@ -414,18 +490,14 @@ public class AppController implements
ownVessel.setAltitude(vessel.getAltitude());
}
Log.i(TAG, "NMEA данные обновлены: course=" + vessel.getCourse() +
", speed=" + vessel.getSpeed() +
", satellites=" + vessel.getSatellites());
// Сокращаем шум логов: сводка NMEA обновлений убрана
// Обновляем карту в главном потоке
if (mapInterface != null) {
Log.i(TAG, "Обновляем позицию на карте из NMEA...");
new android.os.Handler(android.os.Looper.getMainLooper()).post(() -> {
// Сокращаем шум логов: убираем информационные логи карты
uiHandler.post(() -> {
try {
Log.i(TAG, "Вызываем mapInterface.updateOwnVesselPosition из NMEA...");
mapInterface.updateOwnVesselPosition(ownVessel);
Log.i(TAG, "Позиция на карте обновлена из NMEA");
} catch (Exception e) {
Log.e(TAG, "Ошибка обновления позиции на карте из NMEA: " + e.getMessage(), e);
}
@@ -440,7 +512,7 @@ public class AppController implements
@Override
public void onDOPUpdated(double pdop, double hdop, double vdop) {
Log.i(TAG, "📊 DOP обновлен: PDOP=" + pdop + ", HDOP=" + hdop + ", VDOP=" + vdop);
// Убираем шумный лог DOP обновлений
// Обновляем DOP значения
ownVessel.setPdop(pdop);
@@ -486,15 +558,15 @@ public class AppController implements
Log.e(TAG, "Ошибка апсерта AIS в БД: " + e.getMessage(), e);
}
if (mapInterface != null) {
// Используем Handler для выполнения в главном потоке
new android.os.Handler(android.os.Looper.getMainLooper()).post(() -> {
try {
mapInterface.updateAISVesselPosition(existingVessel);
} catch (Exception e) {
Log.e(TAG, "Ошибка обновления позиции AIS судна на карте: " + e.getMessage(), e);
}
});
// Добавляем точку в путь AIS судна
addAISVesselPathPoint(existingVessel);
// Уведомляем UI Coordinator об обновлении AIS судна
if (uiDataNotifier != null) {
Log.d(TAG, "Уведомляем UI Coordinator об обновлении AIS судна: " + existingVessel.getMmsi());
uiDataNotifier.onAISVesselChanged(existingVessel);
} else {
Log.w(TAG, "uiDataNotifier не установлен, пропускаем AIS обновление");
}
} else {
// Добавляем новое судно
@@ -522,15 +594,15 @@ public class AppController implements
Log.e(TAG, "Ошибка апсерта AIS в БД: " + e.getMessage(), e);
}
if (mapInterface != null) {
// Используем Handler для выполнения в главном потоке
new android.os.Handler(android.os.Looper.getMainLooper()).post(() -> {
try {
mapInterface.addAISVesselMarker(vessel);
} catch (Exception e) {
Log.e(TAG, "Ошибка добавления AIS судна на карту: " + e.getMessage(), e);
}
});
// Добавляем точку в путь нового AIS судна
addAISVesselPathPoint(vessel);
// Уведомляем UI Coordinator о новом AIS судне
if (uiDataNotifier != null) {
Log.d(TAG, "Уведомляем UI Coordinator о новом AIS судне: " + vessel.getMmsi());
uiDataNotifier.onAISVesselChanged(vessel);
} else {
Log.w(TAG, "uiDataNotifier не установлен, пропускаем добавление AIS судна");
}
}
@@ -553,7 +625,8 @@ public class AppController implements
float azimuth = (float) ownVessel.getCourse();
List<AISVessel> nearbyVessels = getNearbyVessels();
new android.os.Handler(android.os.Looper.getMainLooper()).post(() -> {
// Используем существующий uiHandler вместо создания нового
uiHandler.post(() -> {
((ExtendedUIUpdateCallback) uiUpdateCallback).onUpdateCompass(azimuth, nearbyVessels);
});
}
@@ -580,11 +653,29 @@ public class AppController implements
@Override
public void onDataReceived(String data, String sourceAddress, int sourcePort) {
Log.d(TAG, "UDP данные получены от " + sourceAddress + ":" + sourcePort);
// Диагностика: логируем каждые 10 секунд
long now = System.currentTimeMillis();
if (now - lastServiceLogTime > 10000) {
Log.d(TAG, "📡 AppController: UDP данные получены от " + sourceAddress + ":" + sourcePort);
lastServiceLogTime = now;
}
// Парсим полученные данные как NMEA
nmeaParser.parseNMEA(data);
// Обновляем метки времени по префиксу
// Парсим полученные данные как NMEA В ФОНОВОМ ПОТОКЕ
executor.execute(() -> {
try {
nmeaParser.parseNMEA(data);
// Диагностика: логируем каждые 10 секунд
long now2 = System.currentTimeMillis();
if (now2 - lastServiceLogTime > 10000) {
Log.d(TAG, "✅ AppController: UDP NMEA обработано в фоновом потоке");
lastServiceLogTime = now2;
}
} catch (Exception e) {
Log.e(TAG, "❌ Ошибка парсинга UDP NMEA в фоновом потоке: " + e.getMessage(), e);
}
});
// Обновляем метки времени по префиксу в UI потоке (быстрая операция)
updateLastMessageAgesFromRaw(data);
}
@@ -602,19 +693,38 @@ public class AppController implements
@Override
public void onNMEAMessage(String message, long timestamp) {
Log.i(TAG, "📱 Android NMEA сообщение получено в AppController: " + message);
// Диагностика: логируем каждые 10 секунд
long now = System.currentTimeMillis();
if (now - lastServiceLogTime > 10000) {
Log.d(TAG, "📱 AppController: Android NMEA сообщение получено");
lastServiceLogTime = now;
}
// Парсим полученные данные как NMEA
nmeaParser.parseNMEA(message);
// Парсим полученные данные как NMEA В ФОНОВОМ ПОТОКЕ
executor.execute(() -> {
try {
nmeaParser.parseNMEA(message);
// Диагностика: логируем каждые 10 секунд
long now2 = System.currentTimeMillis();
if (now2 - lastServiceLogTime > 10000) {
Log.d(TAG, "✅ AppController: NMEA обработано в фоновом потоке");
lastServiceLogTime = now2;
}
} catch (Exception e) {
Log.e(TAG, "❌ Ошибка парсинга NMEA в фоновом потоке: " + e.getMessage(), e);
}
});
// Обновляем метки времени в UI потоке (быстрая операция)
if (message != null) {
String trimmed = message.trim();
if (!trimmed.isEmpty()) {
char c = trimmed.charAt(0);
long now = android.os.SystemClock.elapsedRealtime();
long now3 = android.os.SystemClock.elapsedRealtime();
if (c == '$') {
lastGPSMessageRealtimeMs = now;
lastGPSMessageRealtimeMs = now3;
} else if (c == '!') {
lastAISMessageRealtimeMs = now;
lastAISMessageRealtimeMs = now3;
}
}
}
@@ -681,32 +791,38 @@ public class AppController implements
* Очищает все AIS суда
*/
public void clearAISVessels() {
Log.i(TAG, "Очищаем AIS суда из контроллера");
// Очищаем локальные данные
aisVessels.clear();
if (mapInterface != null) {
// Используем Handler для выполнения в главном потоке
new android.os.Handler(android.os.Looper.getMainLooper()).post(() -> {
try {
mapInterface.clearAISVesselMarkers();
} catch (Exception e) {
Log.e(TAG, "Ошибка очистки AIS судов на карте: " + e.getMessage(), e);
}
});
// Уведомляем UI Coordinator о необходимости очистки карты
if (uiDataNotifier != null) {
Log.d(TAG, "Уведомляем UI Coordinator об очистке AIS судов");
// TODO: Добавить метод очистки всех AIS судов в UIDataChangeNotifier
// Пока что очищаем через individual removals
Log.i(TAG, "Individual AIS removal через uiDataNotifier еще не реализован");
} else {
Log.w(TAG, "uiDataNotifier не установлен, очистка AIS судов пропущена");
}
// Очищаем AIS path controllers
aisPathControllers.clear();
}
/**
* Центрирует карту на позиции нашего судна
*/
public void centerOnOwnVessel() {
if (mapInterface != null && ownVessel != null) {
// Используем Handler для выполнения в главном потоке
new android.os.Handler(android.os.Looper.getMainLooper()).post(() -> {
try {
mapInterface.centerOnPosition(ownVessel.getLatitude(), ownVessel.getLongitude());
} catch (Exception e) {
Log.e(TAG, "Ошибка центрирования карты: " + e.getMessage(), e);
}
});
if (ownVessel != null) {
Log.d(TAG, "Запрос центрирования карты на судне: " + ownVessel.getLatitude() + "," + ownVessel.getLongitude());
// Уведомляем UI Coordinator о необходимости центрирования карты
if (uiDataNotifier != null) {
uiDataNotifier.onRequestCenterMap(ownVessel.getLatitude(), ownVessel.getLongitude());
} else {
Log.w(TAG, "uiDataNotifier не установлен, центрирование карты пропущено");
}
}
}
@@ -761,6 +877,11 @@ public class AppController implements
stopAllListeners();
stopDatabaseCleanup();
// Очищаем Handler'ы для предотвращения утечек памяти
if (uiHandler != null) {
uiHandler.removeCallbacksAndMessages(null);
}
if (udpListener != null) {
udpListener.cleanup();
}
@@ -924,4 +1045,142 @@ public class AppController implements
dataMode != null ? dataMode : "не установлен"
);
}
// ===== Методы для работы с путем судна =====
/**
* Получает контроллер пути судна
*/
public VesselPathController getPathController() {
return pathController;
}
/**
* Получает информацию о пути судна
*/
public String getVesselPathInfo() {
if (pathController != null) {
return pathController.getPathInfo();
}
return "Контроллер пути не инициализирован";
}
/**
* Очищает путь судна
*/
public void clearVesselPath() {
if (pathController != null) {
pathController.clearPath();
Log.i(TAG, "Путь судна очищен");
}
}
/**
* Сохраняет путь судна
*/
public void saveVesselPath() {
if (pathController != null) {
Log.d(TAG, "Сохранение пути судна: " + pathController.getPathInfo());
}
}
/**
* Добавляет точку в путь AIS судна
*/
private void addAISVesselPathPoint(AISVessel vessel) {
if (vessel == null || vessel.getMmsi() == null) {
return;
}
// Проверяем валидность координат
if (!isValidCoordinates(vessel.getLatitude(), vessel.getLongitude())) {
Log.d(TAG, "addAISVesselPathPoint: AIS vessel " + vessel.getMmsi() +
" has invalid coordinates " + vessel.getLatitude() + "," + vessel.getLongitude() +
" - skipping path point");
return;
}
String mmsi = vessel.getMmsi();
// Получаем или создаем VesselPathController для этого AIS судна
VesselPathController aisPathController = aisPathControllers.get(mmsi);
if (aisPathController == null) {
// Ограничиваем количество трекеров для производительности
if (aisPathControllers.size() >= 20) {
Log.w(TAG, "Достигнуто максимальное количество AIS трекеров (20), пропускаем создание для " + mmsi);
return;
}
aisPathController = new VesselPathController(context, settingsManager, mmsi);
aisPathControllers.put(mmsi, aisPathController);
Log.d(TAG, "Создан VesselPathController для AIS судна " + mmsi + " (всего трекеров: " + aisPathControllers.size() + ")");
}
// Добавляем точку в путь
boolean pointAdded = aisPathController.addPathPoint(
vessel.getLongitude(),
vessel.getLatitude(),
(float) vessel.getSpeed()
);
if (pointAdded) {
Log.d(TAG, "Точка пути добавлена для AIS " + mmsi + ": " + aisPathController.getPathPointsCount() + " точек");
}
}
/**
* Проверяет валидность координат
* Игнорирует координаты 0,0 и 181,91 (невалидные значения AIS)
*/
private boolean isValidCoordinates(double latitude, double longitude) {
// Проверяем на нулевые координаты
if (latitude == 0.0 && longitude == 0.0) {
return false;
}
// Проверяем на невалидные координаты AIS (181, 91)
if (latitude == 91.0 && longitude == 181.0) {
return false;
}
// Проверяем на стандартные границы координат
if (latitude < -90.0 || latitude > 90.0) {
return false;
}
if (longitude < -180.0 || longitude > 180.0) {
return false;
}
return true;
}
/**
* Получает VesselPathController для AIS судна
*/
public VesselPathController getAISVesselPathController(String mmsi) {
return aisPathControllers.get(mmsi);
}
/**
* Очищает путь AIS судна
*/
public void clearAISVesselPath(String mmsi) {
VesselPathController aisPathController = aisPathControllers.get(mmsi);
if (aisPathController != null) {
aisPathController.clearPath();
Log.d(TAG, "Путь AIS судна " + mmsi + " очищен");
}
}
/**
* Очищает все пути AIS судов
*/
public void clearAllAISVesselPaths() {
for (VesselPathController controller : aisPathControllers.values()) {
controller.clearPath();
}
aisPathControllers.clear();
Log.d(TAG, "Все пути AIS судов очищены");
}
}