generated from Grigo/AndroidTemplate
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:
@@ -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 судов очищены");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user