generated from Grigo/AndroidTemplate
b5aee265bc
Архитектурные улучшения: - Внедрен 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
1187 lines
50 KiB
Java
1187 lines
50 KiB
Java
package com.grigowashere.aismap.controllers;
|
||
|
||
import android.content.Context;
|
||
import android.util.Log;
|
||
import com.grigowashere.aismap.models.Vessel;
|
||
import com.grigowashere.aismap.models.AISVessel;
|
||
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;
|
||
|
||
/**
|
||
* Главный контроллер приложения
|
||
* Координирует работу всех компонентов
|
||
* Использует гибридный подход: координаты через Location API, остальное через NMEA
|
||
*/
|
||
public class AppController implements
|
||
NMEAParser.NMEAParserListener,
|
||
UDPListener.UDPListenerCallback,
|
||
AndroidNMEAListener.NMEAMessageCallback,
|
||
GPSLocationListener.LocationCallback,
|
||
MapInterface.MarkerClickListener {
|
||
|
||
private static final String TAG = "AppController";
|
||
|
||
private Context context;
|
||
private NMEAParser nmeaParser;
|
||
private UDPListener udpListener;
|
||
private AndroidNMEAListener androidNmeaListener;
|
||
private GPSLocationListener gpsLocationListener;
|
||
private MapInterface mapInterface;
|
||
|
||
private Vessel ownVessel;
|
||
private List<AISVessel> aisVessels;
|
||
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;
|
||
private boolean isUDPNMEAEnabled;
|
||
private boolean isGPSLocationEnabled;
|
||
private int udpPort;
|
||
private String dataMode;
|
||
|
||
// Время последнего получения сообщений ($ GPS) и (! AIS) в elapsedRealtime
|
||
private long lastGPSMessageRealtimeMs;
|
||
private long lastAISMessageRealtimeMs;
|
||
|
||
// Периодическая очистка БД от устаревших AIS целей
|
||
private android.os.Handler dbCleanupHandler;
|
||
private Runnable dbCleanupRunnable;
|
||
private static final long DB_CLEANUP_INTERVAL = 60000; // 1 минута
|
||
|
||
// Единый 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);
|
||
}
|
||
|
||
/**
|
||
* Расширенный интерфейс для дополнительных UI событий
|
||
*/
|
||
public interface ExtendedUIUpdateCallback extends UIUpdateCallback {
|
||
void onShowOwnVesselBottomSheet();
|
||
void onShowAISVesselInfo(AISVessel vessel);
|
||
void onUpdateCompass(float azimuth, List<AISVessel> nearbyVessels);
|
||
}
|
||
|
||
public AppController(Context context) {
|
||
this.context = context;
|
||
this.ownVessel = new Vessel();
|
||
this.aisVessels = new ArrayList<>();
|
||
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();
|
||
}
|
||
|
||
/**
|
||
* Инициализирует все контроллеры
|
||
*/
|
||
private void initializeControllers() {
|
||
// Инициализация парсера NMEA
|
||
nmeaParser = new NMEAParser();
|
||
nmeaParser.setListener(this);
|
||
|
||
// Инициализация GPS Location Listener (для координат)
|
||
gpsLocationListener = new GPSLocationListener(context);
|
||
gpsLocationListener.setCallback(this);
|
||
|
||
// Связываем NMEA парсер с GPS Location Listener для гибридного режима
|
||
nmeaParser.setGPSLocationListener(gpsLocationListener);
|
||
nmeaParser.setHybridMode(true);
|
||
|
||
// Инициализация UDP слушателя (порт 10110 - стандартный для AIS)
|
||
udpPort = 10110;
|
||
udpListener = new UDPListener(udpPort);
|
||
udpListener.setCallback(this);
|
||
|
||
// Инициализация Android NMEA слушателя (для курса, скорости, DOP)
|
||
androidNmeaListener = new AndroidNMEAListener(context);
|
||
androidNmeaListener.setCallback(this);
|
||
|
||
// Восстанавливаем данные из БД при старте АСИНХРОННО
|
||
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.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);
|
||
}
|
||
});
|
||
}
|
||
|
||
/**
|
||
* Устанавливает интерфейс карты
|
||
*/
|
||
public void setMapInterface(MapInterface mapInterface) {
|
||
Log.i(TAG, "setMapInterface вызван: " + (mapInterface != null ? "mapInterface установлен" : "mapInterface == null"));
|
||
this.mapInterface = mapInterface;
|
||
if (mapInterface != null) {
|
||
Log.i(TAG, "Устанавливаем MarkerClickListener в MapInterface");
|
||
mapInterface.setMarkerClickListener(this);
|
||
Log.i(TAG, "MarkerClickListener установлен, теперь можно создавать маркеры");
|
||
|
||
// Уведомляем 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 не установлен при восстановлении данных - маркеры НЕ будут восстановлены!");
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Устанавливает индикатор изменений данных для централизованного 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;
|
||
}
|
||
|
||
/**
|
||
* Запускает все слушатели
|
||
*/
|
||
public void startAllListeners() {
|
||
// GPS Location Listener запускается в главном потоке
|
||
if (isGPSLocationEnabled) {
|
||
gpsLocationListener.startListening();
|
||
}
|
||
|
||
// Android NMEA слушатель должен запускаться в главном потоке
|
||
if (isAndroidNMEAEnabled) {
|
||
androidNmeaListener.startListening();
|
||
}
|
||
|
||
// UDP слушатель запускается в фоновом потоке
|
||
if (isUDPEnabled) {
|
||
executor.execute(() -> {
|
||
udpListener.start();
|
||
});
|
||
}
|
||
|
||
// Запускаем периодическую очистку БД от устаревших AIS целей
|
||
startDatabaseCleanup();
|
||
}
|
||
|
||
|
||
|
||
/**
|
||
* Останавливает все слушатели
|
||
*/
|
||
public void stopAllListeners() {
|
||
// Останавливаем периодическую очистку БД
|
||
stopDatabaseCleanup();
|
||
|
||
executor.execute(() -> {
|
||
udpListener.stop();
|
||
androidNmeaListener.stopListening();
|
||
gpsLocationListener.stopListening();
|
||
});
|
||
}
|
||
|
||
/**
|
||
* Включает/выключает UDP слушатель
|
||
*/
|
||
public void setUDPEnabled(boolean enabled) {
|
||
this.isUDPEnabled = enabled;
|
||
if (enabled && !udpListener.isRunning()) {
|
||
udpListener.start();
|
||
} else if (!enabled && udpListener.isRunning()) {
|
||
udpListener.stop();
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Включает/выключает Android NMEA слушатель
|
||
*/
|
||
public void setAndroidNMEAEnabled(boolean enabled) {
|
||
Log.i(TAG, "🔄 setAndroidNMEAEnabled: " + enabled);
|
||
this.isAndroidNMEAEnabled = enabled;
|
||
|
||
// Android NMEA слушатель управляется в главном потоке
|
||
if (enabled && !androidNmeaListener.isListening()) {
|
||
Log.i(TAG, "🚀 Запускаем Android NMEA слушатель...");
|
||
boolean success = androidNmeaListener.startListening();
|
||
if (success) {
|
||
Log.i(TAG, "✅ Android NMEA слушатель успешно запущен");
|
||
} else {
|
||
Log.e(TAG, "❌ Не удалось запустить Android NMEA слушатель");
|
||
}
|
||
} else if (!enabled && androidNmeaListener.isListening()) {
|
||
Log.i(TAG, "⏹️ Останавливаем Android NMEA слушатель...");
|
||
androidNmeaListener.stopListening();
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Включает/выключает GPS Location слушатель
|
||
*/
|
||
public void setGPSLocationEnabled(boolean enabled) {
|
||
Log.i(TAG, "🔄 setGPSLocationEnabled: " + enabled);
|
||
this.isGPSLocationEnabled = enabled;
|
||
|
||
if (enabled && !gpsLocationListener.isListening()) {
|
||
Log.i(TAG, "🚀 Запускаем GPS Location слушатель...");
|
||
boolean success = gpsLocationListener.startListening();
|
||
if (success) {
|
||
Log.i(TAG, "✅ GPS Location слушатель успешно запущен");
|
||
} else {
|
||
Log.e(TAG, "❌ Не удалось запустить GPS Location слушатель");
|
||
}
|
||
} else if (!enabled && gpsLocationListener.isListening()) {
|
||
Log.i(TAG, "⏹️ Останавливаем GPS Location слушатель...");
|
||
gpsLocationListener.stopListening();
|
||
}
|
||
}
|
||
|
||
|
||
|
||
/**
|
||
* Отправляет данные по UDP
|
||
*/
|
||
public void sendUDPData(String data, String address, int port) {
|
||
udpListener.sendData(data, address, port);
|
||
}
|
||
|
||
/**
|
||
* Проверяет, включен ли UDP слушатель
|
||
*/
|
||
public boolean isUDPEnabled() {
|
||
return isUDPEnabled;
|
||
}
|
||
|
||
/**
|
||
* Проверяет, включен ли Android NMEA слушатель
|
||
*/
|
||
public boolean isAndroidNMEAEnabled() {
|
||
return isAndroidNMEAEnabled;
|
||
}
|
||
|
||
/**
|
||
* Проверяет, включен ли GPS Location слушатель
|
||
*/
|
||
public boolean isGPSLocationEnabled() {
|
||
return isGPSLocationEnabled;
|
||
}
|
||
|
||
/**
|
||
* Обновляет данные нашего судна при клике по маркеру
|
||
*/
|
||
private void updateOwnVesselData(Vessel vessel) {
|
||
if (vessel != null) {
|
||
// Обновляем только те данные, которые могут быть актуальными
|
||
// Координаты и основная информация уже обновляются через GPS
|
||
if (vessel.getCourse() > 0) {
|
||
ownVessel.setCourse(vessel.getCourse());
|
||
updateCompass(); // Обновляем компас при изменении курса
|
||
}
|
||
if (vessel.getSpeed() > 0) {
|
||
ownVessel.setSpeed(vessel.getSpeed());
|
||
}
|
||
if (vessel.getSatellites() > 0) {
|
||
ownVessel.setSatellites(vessel.getSatellites());
|
||
}
|
||
if (vessel.getAltitude() != 0) {
|
||
ownVessel.setAltitude(vessel.getAltitude());
|
||
}
|
||
if (vessel.getPdop() > 0) {
|
||
ownVessel.setPdop(vessel.getPdop());
|
||
ownVessel.setHdop(vessel.getHdop());
|
||
ownVessel.setVdop(vessel.getVdop());
|
||
}
|
||
}
|
||
}
|
||
|
||
// Реализация LocationCallback (GPS Location Listener)
|
||
|
||
@Override
|
||
public void onLocationUpdated(Vessel vessel) {
|
||
Log.i(TAG, "📍 GPS Location обновлен: lat=" + vessel.getLatitude() +
|
||
", lon=" + vessel.getLongitude() +
|
||
", accuracy=" + vessel.getAccuracy() + "м");
|
||
|
||
// Обновляем координаты нашего судна
|
||
ownVessel.setLatitude(vessel.getLatitude());
|
||
ownVessel.setLongitude(vessel.getLongitude());
|
||
ownVessel.setAccuracy(vessel.getAccuracy());
|
||
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();
|
||
ve.latitude = ownVessel.getLatitude();
|
||
ve.longitude = ownVessel.getLongitude();
|
||
ve.accuracy = ownVessel.getAccuracy();
|
||
ve.fixTime = ownVessel.getFixTime();
|
||
repository.upsertOwnVessel(ve);
|
||
} catch (Exception e) {
|
||
Log.e(TAG, "Ошибка сохранения позиции в БД: " + e.getMessage(), e);
|
||
}
|
||
|
||
// Обновляем UI через callback
|
||
if (uiUpdateCallback != null) {
|
||
uiUpdateCallback.onVesselPositionUpdated(ownVessel);
|
||
}
|
||
|
||
// Уведомляем UI Coordinator об изменении позиции судна (централизованный throttling)
|
||
if (uiDataNotifier != null) {
|
||
Log.d(TAG, "Уведомляем UI Coordinator об изменении позиции судна");
|
||
uiDataNotifier.onVesselPositionChanged(ownVessel);
|
||
} else {
|
||
Log.w(TAG, "uiDataNotifier не установлен, пропускаем UI обновление");
|
||
}
|
||
}
|
||
|
||
@Override
|
||
public void onGPSStatusChanged(int status) {
|
||
Log.i(TAG, "GPS статус изменился: " + status);
|
||
}
|
||
|
||
// Реализация NMEAParserListener
|
||
|
||
@Override
|
||
public void onVesselUpdated(Vessel vessel) {
|
||
// Сокращаем шум логов: подробности обновления судна убраны
|
||
|
||
// Обновляем координаты, если они есть (для режима "только NMEA")
|
||
if (vessel.getLatitude() != 0 && vessel.getLongitude() != 0) {
|
||
ownVessel.setLatitude(vessel.getLatitude());
|
||
ownVessel.setLongitude(vessel.getLongitude());
|
||
// Сокращаем шум логов: координаты обновлены (без детализации)
|
||
|
||
// Добавляем точку в путь судна
|
||
if (pathController != null) {
|
||
boolean pointAdded = pathController.addPathPoint(
|
||
vessel.getLongitude(),
|
||
vessel.getLatitude(),
|
||
(float) vessel.getSpeed()
|
||
);
|
||
// Убираем лог о добавлении каждой точки пути
|
||
}
|
||
}
|
||
|
||
// Обновляем дополнительные данные
|
||
if (vessel.getCourse() > 0) {
|
||
ownVessel.setCourse(vessel.getCourse());
|
||
updateCompass(); // Обновляем компас при изменении курса
|
||
}
|
||
if (vessel.getSpeed() > 0) {
|
||
ownVessel.setSpeed(vessel.getSpeed());
|
||
}
|
||
if (vessel.getSatellites() > 0) {
|
||
ownVessel.setSatellites(vessel.getSatellites());
|
||
}
|
||
if (vessel.getAltitude() != 0) {
|
||
ownVessel.setAltitude(vessel.getAltitude());
|
||
}
|
||
|
||
// Сокращаем шум логов: сводка NMEA обновлений убрана
|
||
|
||
// Обновляем карту в главном потоке
|
||
if (mapInterface != null) {
|
||
// Сокращаем шум логов: убираем информационные логи карты
|
||
uiHandler.post(() -> {
|
||
try {
|
||
mapInterface.updateOwnVesselPosition(ownVessel);
|
||
} catch (Exception e) {
|
||
Log.e(TAG, "Ошибка обновления позиции на карте из NMEA: " + e.getMessage(), e);
|
||
}
|
||
});
|
||
}
|
||
|
||
// Обновляем UI
|
||
if (uiUpdateCallback != null) {
|
||
uiUpdateCallback.onVesselPositionUpdated(ownVessel);
|
||
}
|
||
}
|
||
|
||
@Override
|
||
public void onDOPUpdated(double pdop, double hdop, double vdop) {
|
||
// Убираем шумный лог DOP обновлений
|
||
|
||
// Обновляем DOP значения
|
||
ownVessel.setPdop(pdop);
|
||
ownVessel.setHdop(hdop);
|
||
ownVessel.setVdop(vdop);
|
||
|
||
// Обновляем UI
|
||
if (uiUpdateCallback != null) {
|
||
uiUpdateCallback.onGPSQualityUpdated(ownVessel);
|
||
}
|
||
}
|
||
|
||
@Override
|
||
public void onAISVesselUpdated(AISVessel vessel) {
|
||
// Проверяем, есть ли уже такое судно
|
||
AISVessel existingVessel = findAISVesselByMMSI(vessel.getMmsi());
|
||
|
||
if (existingVessel != null) {
|
||
// Если пришло новое safety-сообщение (тип 14), уведомим пользователя
|
||
if (vessel.getLastSafetyMessage() != null && !vessel.getLastSafetyMessage().isEmpty()) {
|
||
String prev = existingVessel.getLastSafetyMessage();
|
||
String curr = vessel.getLastSafetyMessage();
|
||
if (prev == null || !prev.equals(curr)) {
|
||
if (notificationService != null && notificationService.areNotificationsEnabled()) {
|
||
notificationService.notifySafetyMessage(vessel.getMmsi(), curr);
|
||
}
|
||
}
|
||
existingVessel.setLastSafetyMessage(curr);
|
||
}
|
||
// Обновляем существующее судно
|
||
existingVessel.updatePosition(
|
||
vessel.getLatitude(),
|
||
vessel.getLongitude(),
|
||
vessel.getCourse(),
|
||
vessel.getSpeed()
|
||
);
|
||
try {
|
||
// Используем маппер для полной конвертации всех полей
|
||
com.grigowashere.aismap.data.entity.AISVesselEntity entity = AISVesselMapper.toEntity(existingVessel);
|
||
repository.upsertAIS(entity);
|
||
Log.d(TAG, "AIS судно сохранено в БД с полными данными: " + existingVessel.getMmsi());
|
||
} 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 {
|
||
// Добавляем новое судно
|
||
aisVessels.add(vessel);
|
||
|
||
// Если это новое судно сразу пришло с safety-сообщением — уведомим
|
||
if (vessel.getLastSafetyMessage() != null && !vessel.getLastSafetyMessage().isEmpty()) {
|
||
if (notificationService != null && notificationService.areNotificationsEnabled()) {
|
||
notificationService.notifySafetyMessage(vessel.getMmsi(), vessel.getLastSafetyMessage());
|
||
}
|
||
}
|
||
|
||
// Воспроизводим уведомление о новой цели
|
||
if (notificationService != null && notificationService.areNotificationsEnabled()) {
|
||
notificationService.notifyNewAISTarget();
|
||
Log.i(TAG, "🔔 Уведомление о новой AIS цели: " + vessel.getMmsi());
|
||
}
|
||
|
||
try {
|
||
// Используем маппер для полной конвертации всех полей
|
||
com.grigowashere.aismap.data.entity.AISVesselEntity entity = AISVesselMapper.toEntity(vessel);
|
||
repository.upsertAIS(entity);
|
||
Log.d(TAG, "Новое AIS судно сохранено в БД с полными данными: " + vessel.getMmsi());
|
||
} 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 судна");
|
||
}
|
||
}
|
||
|
||
// Обновляем компас с ближайшими судами
|
||
updateCompass();
|
||
|
||
Log.i(TAG, "AIS судно обновлено: " + vessel);
|
||
}
|
||
|
||
@Override
|
||
public void onParseError(String error) {
|
||
Log.e(TAG, "Ошибка парсинга NMEA: " + error);
|
||
}
|
||
|
||
/**
|
||
* Обновляет компас с текущим азимутом и ближайшими судами
|
||
*/
|
||
private void updateCompass() {
|
||
if (uiUpdateCallback instanceof ExtendedUIUpdateCallback) {
|
||
float azimuth = (float) ownVessel.getCourse();
|
||
List<AISVessel> nearbyVessels = getNearbyVessels();
|
||
|
||
// Используем существующий uiHandler вместо создания нового
|
||
uiHandler.post(() -> {
|
||
((ExtendedUIUpdateCallback) uiUpdateCallback).onUpdateCompass(azimuth, nearbyVessels);
|
||
});
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Получает список ближайших судов (в пределах 10 км)
|
||
*/
|
||
private List<AISVessel> getNearbyVessels() {
|
||
List<AISVessel> nearby = new ArrayList<>();
|
||
double maxDistance = 10000; // 10 км в метрах
|
||
|
||
for (AISVessel vessel : aisVessels) {
|
||
double distance = com.grigowashere.aismap.utils.GeoUtils.calculateDistance(ownVessel, vessel);
|
||
if (distance <= maxDistance) {
|
||
nearby.add(vessel);
|
||
}
|
||
}
|
||
|
||
return nearby;
|
||
}
|
||
|
||
// Реализация UDPListenerCallback
|
||
|
||
@Override
|
||
public void onDataReceived(String data, String sourceAddress, int sourcePort) {
|
||
// Диагностика: логируем каждые 10 секунд
|
||
long now = System.currentTimeMillis();
|
||
if (now - lastServiceLogTime > 10000) {
|
||
Log.d(TAG, "📡 AppController: UDP данные получены от " + sourceAddress + ":" + sourcePort);
|
||
lastServiceLogTime = now;
|
||
}
|
||
|
||
// Парсим полученные данные как 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);
|
||
}
|
||
|
||
@Override
|
||
public void onUDPError(String error) {
|
||
Log.e(TAG, "UDP ошибка: " + error);
|
||
}
|
||
|
||
@Override
|
||
public void onError(String error) {
|
||
Log.e(TAG, "GPS Location ошибка: " + error);
|
||
}
|
||
|
||
// Реализация NMEAMessageCallback
|
||
|
||
@Override
|
||
public void onNMEAMessage(String message, long timestamp) {
|
||
// Диагностика: логируем каждые 10 секунд
|
||
long now = System.currentTimeMillis();
|
||
if (now - lastServiceLogTime > 10000) {
|
||
Log.d(TAG, "📱 AppController: Android NMEA сообщение получено");
|
||
lastServiceLogTime = now;
|
||
}
|
||
|
||
// Парсим полученные данные как 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 now3 = android.os.SystemClock.elapsedRealtime();
|
||
if (c == '$') {
|
||
lastGPSMessageRealtimeMs = now3;
|
||
} else if (c == '!') {
|
||
lastAISMessageRealtimeMs = now3;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// Реализация MarkerClickListener
|
||
|
||
@Override
|
||
public void onOwnVesselClick(Vessel vessel) {
|
||
Log.i(TAG, "Клик по нашему судну: " + vessel);
|
||
// Уведомляем UI о необходимости показать BottomSheet
|
||
if (uiUpdateCallback != null) {
|
||
Log.i(TAG, "uiUpdateCallback найден, обновляем данные судна");
|
||
// Обновляем данные судна перед показом
|
||
updateOwnVesselData(vessel);
|
||
// Вызываем специальный callback для показа BottomSheet
|
||
if (uiUpdateCallback instanceof ExtendedUIUpdateCallback) {
|
||
Log.i(TAG, "Вызываем onShowOwnVesselBottomSheet");
|
||
((ExtendedUIUpdateCallback) uiUpdateCallback).onShowOwnVesselBottomSheet();
|
||
} else {
|
||
Log.w(TAG, "uiUpdateCallback не является ExtendedUIUpdateCallback");
|
||
}
|
||
} else {
|
||
Log.e(TAG, "uiUpdateCallback == null!");
|
||
}
|
||
}
|
||
|
||
@Override
|
||
public void onAISVesselClick(AISVessel vessel) {
|
||
Log.i(TAG, "Клик по AIS судну: " + vessel);
|
||
// Уведомляем UI о необходимости показать информацию об AIS судне
|
||
if (uiUpdateCallback != null && uiUpdateCallback instanceof ExtendedUIUpdateCallback) {
|
||
((ExtendedUIUpdateCallback) uiUpdateCallback).onShowAISVesselInfo(vessel);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Находит AIS судно по MMSI
|
||
*/
|
||
private AISVessel findAISVesselByMMSI(String mmsi) {
|
||
for (AISVessel vessel : aisVessels) {
|
||
if (mmsi.equals(vessel.getMmsi())) {
|
||
return vessel;
|
||
}
|
||
}
|
||
return null;
|
||
}
|
||
|
||
/**
|
||
* Получает наше судно
|
||
*/
|
||
public Vessel getOwnVessel() {
|
||
return ownVessel;
|
||
}
|
||
|
||
/**
|
||
* Получает список AIS судов
|
||
*/
|
||
public List<AISVessel> getAISVessels() {
|
||
return new ArrayList<>(aisVessels);
|
||
}
|
||
|
||
/**
|
||
* Очищает все AIS суда
|
||
*/
|
||
public void clearAISVessels() {
|
||
Log.i(TAG, "Очищаем AIS суда из контроллера");
|
||
|
||
// Очищаем локальные данные
|
||
aisVessels.clear();
|
||
|
||
// Уведомляем 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 (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 не установлен, центрирование карты пропущено");
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Запускает периодическую очистку БД от устаревших AIS целей
|
||
*/
|
||
public void startDatabaseCleanup() {
|
||
if (dbCleanupHandler != null && dbCleanupRunnable != null) {
|
||
dbCleanupHandler.postDelayed(dbCleanupRunnable, DB_CLEANUP_INTERVAL);
|
||
Log.i(TAG, "Запущена периодическая очистка БД от устаревших AIS целей");
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Останавливает периодическую очистку БД
|
||
*/
|
||
public void stopDatabaseCleanup() {
|
||
if (dbCleanupHandler != null && dbCleanupRunnable != null) {
|
||
dbCleanupHandler.removeCallbacks(dbCleanupRunnable);
|
||
Log.i(TAG, "Остановлена периодическая очистка БД от устаревших AIS целей");
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Выполняет очистку БД от устаревших AIS целей
|
||
*/
|
||
private void performDatabaseCleanup() {
|
||
try {
|
||
com.grigowashere.aismap.utils.SettingsManager settingsManager =
|
||
new com.grigowashere.aismap.utils.SettingsManager(context);
|
||
|
||
int staleRemoveMinutes = settingsManager.getDataStaleRemoveMinutes();
|
||
long thresholdEpochMs = System.currentTimeMillis() - (staleRemoveMinutes * 60 * 1000L);
|
||
|
||
repository.deleteStaleAIS(thresholdEpochMs);
|
||
|
||
Log.i(TAG, "Выполнена очистка БД от AIS целей старше " + staleRemoveMinutes + " минут");
|
||
|
||
// Планируем следующую очистку
|
||
if (dbCleanupHandler != null && dbCleanupRunnable != null) {
|
||
dbCleanupHandler.postDelayed(dbCleanupRunnable, DB_CLEANUP_INTERVAL);
|
||
}
|
||
} catch (Exception e) {
|
||
Log.e(TAG, "Ошибка при очистке БД от устаревших AIS целей: " + e.getMessage(), e);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Освобождает ресурсы
|
||
*/
|
||
public void cleanup() {
|
||
stopAllListeners();
|
||
stopDatabaseCleanup();
|
||
|
||
// Очищаем Handler'ы для предотвращения утечек памяти
|
||
if (uiHandler != null) {
|
||
uiHandler.removeCallbacksAndMessages(null);
|
||
}
|
||
|
||
if (udpListener != null) {
|
||
udpListener.cleanup();
|
||
}
|
||
|
||
if (androidNmeaListener != null) {
|
||
androidNmeaListener.cleanup();
|
||
}
|
||
|
||
if (gpsLocationListener != null) {
|
||
gpsLocationListener.cleanup();
|
||
}
|
||
|
||
if (notificationService != null) {
|
||
notificationService.cleanup();
|
||
}
|
||
|
||
if (executor != null && !executor.isShutdown()) {
|
||
executor.shutdown();
|
||
}
|
||
}
|
||
|
||
// ===== Метки времени последних сообщений ($ и !) =====
|
||
private void updateLastMessageAgesFromRaw(String raw) {
|
||
if (raw == null) return;
|
||
long now = android.os.SystemClock.elapsedRealtime();
|
||
String[] lines = raw.split("\r?\n");
|
||
for (String line : lines) {
|
||
if (line == null) continue;
|
||
String t = line.trim();
|
||
if (t.isEmpty()) continue;
|
||
char c = t.charAt(0);
|
||
if (c == '$') {
|
||
lastGPSMessageRealtimeMs = now;
|
||
break;
|
||
} else if (c == '!') {
|
||
lastAISMessageRealtimeMs = now;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
/** Возвращает секунды с последнего GPS ($) сообщения; -1 если не было */
|
||
public int getSecondsSinceLastGPSMessage() {
|
||
if (lastGPSMessageRealtimeMs <= 0) return -1;
|
||
long diff = android.os.SystemClock.elapsedRealtime() - lastGPSMessageRealtimeMs;
|
||
if (diff < 0) return 0;
|
||
return (int)(diff / 1000L);
|
||
}
|
||
|
||
/** Возвращает секунды с последнего AIS (!) сообщения; -1 если не было */
|
||
public int getSecondsSinceLastAISMessage() {
|
||
if (lastAISMessageRealtimeMs <= 0) return -1;
|
||
long diff = android.os.SystemClock.elapsedRealtime() - lastAISMessageRealtimeMs;
|
||
if (diff < 0) return 0;
|
||
return (int)(diff / 1000L);
|
||
}
|
||
|
||
// Методы для управления настройками
|
||
|
||
/**
|
||
* Устанавливает UDP порт
|
||
*/
|
||
public void setUDPPort(int port) {
|
||
if (port < 1 || port > 65535) {
|
||
Log.w(TAG, "Некорректный UDP порт: " + port);
|
||
return;
|
||
}
|
||
|
||
this.udpPort = port;
|
||
Log.i(TAG, "UDP порт установлен: " + port);
|
||
|
||
// Если UDP слушатель уже создан, нужно будет его пересоздать
|
||
if (udpListener != null && udpListener.getPort() != port) {
|
||
Log.i(TAG, "UDP порт изменен, потребуется перезапуск UDP слушателя");
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Получает текущий UDP порт
|
||
*/
|
||
public int getUDPPort() {
|
||
return udpPort;
|
||
}
|
||
|
||
/**
|
||
* Включает/выключает UDP NMEA
|
||
*/
|
||
public void setUDPNMEAEnabled(boolean enabled) {
|
||
this.isUDPNMEAEnabled = enabled;
|
||
Log.i(TAG, "UDP NMEA: " + (enabled ? "включен" : "выключен"));
|
||
}
|
||
|
||
/**
|
||
* Проверяет, включен ли UDP NMEA
|
||
*/
|
||
public boolean isUDPNMEAEnabled() {
|
||
return isUDPNMEAEnabled;
|
||
}
|
||
|
||
/**
|
||
* Устанавливает режим работы с данными
|
||
*/
|
||
public void setDataMode(String mode) {
|
||
this.dataMode = mode;
|
||
Log.i(TAG, "🔄 Режим данных установлен: " + mode);
|
||
|
||
// Применяем режим к NMEA парсеру
|
||
if (nmeaParser != null) {
|
||
boolean hybridMode = "hybrid".equals(mode);
|
||
nmeaParser.setHybridMode(hybridMode);
|
||
Log.i(TAG, "📍 Гибридный режим NMEA парсера: " + hybridMode);
|
||
Log.i(TAG, "📍 В режиме '" + mode + "' координаты будут " +
|
||
(hybridMode ? "браться из Android GPS API" : "браться из NMEA сообщений"));
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Получает текущий режим работы с данными
|
||
*/
|
||
public String getDataMode() {
|
||
return dataMode;
|
||
}
|
||
|
||
/**
|
||
* Перезапускает UDP слушатель с новым портом
|
||
*/
|
||
public void restartUDPListener() {
|
||
if (udpListener != null) {
|
||
Log.i(TAG, "Перезапускаем UDP слушатель с портом: " + udpPort);
|
||
|
||
// Останавливаем текущий слушатель
|
||
udpListener.stop();
|
||
udpListener.cleanup();
|
||
|
||
// Создаем новый слушатель с новым портом
|
||
udpListener = new UDPListener(udpPort);
|
||
udpListener.setCallback(this);
|
||
|
||
// Запускаем, если UDP включен
|
||
if (isUDPEnabled) {
|
||
udpListener.start();
|
||
Log.i(TAG, "UDP слушатель перезапущен на порту: " + udpPort);
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Получает статус всех настроек
|
||
*/
|
||
public String getSettingsStatus() {
|
||
return String.format(
|
||
"UDP: порт=%d, включен=%s, NMEA=%s\n" +
|
||
"Android NMEA: %s\n" +
|
||
"GPS Location: %s\n" +
|
||
"Режим данных: %s",
|
||
udpPort,
|
||
isUDPEnabled ? "да" : "нет",
|
||
isUDPNMEAEnabled ? "включен" : "выключен",
|
||
isAndroidNMEAEnabled ? "включен" : "выключен",
|
||
isGPSLocationEnabled ? "включен" : "выключен",
|
||
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 судов очищены");
|
||
}
|
||
}
|