Initial commit: AIS Map Android application

This commit is contained in:
ОС Программист
2025-09-02 15:58:16 +03:00
commit 629b403dd2
78 changed files with 9209 additions and 0 deletions
@@ -0,0 +1,595 @@
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 java.util.List;
import java.util.ArrayList;
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 boolean isUDPEnabled;
private boolean isAndroidNMEAEnabled;
private boolean isGPSLocationEnabled;
// Callback для обновления UI
private UIUpdateCallback uiUpdateCallback;
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();
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)
udpListener = new UDPListener(10110);
udpListener.setCallback(this);
// Инициализация Android NMEA слушателя (для курса, скорости, DOP)
androidNmeaListener = new AndroidNMEAListener(context);
androidNmeaListener.setCallback(this);
}
/**
* Устанавливает интерфейс карты
*/
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 установлен, теперь можно создавать маркеры");
}
}
/**
* Устанавливает callback для обновления UI
*/
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();
});
}
// Тестируем NMEA парсер (временно)
testNMEAParser();
}
/**
* Тестирует NMEA парсер (временно для отладки)
*/
private void testNMEAParser() {
Log.i(TAG, "Тестируем NMEA парсер...");
// Тестовые NMEA сообщения
String[] testMessages = {
"$GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47",
"$GPRMC,123519,A,4807.038,N,01131.000,E,022.4,084.4,230394,003.1,W*6A",
"$GPVTG,054.7,T,034.4,M,005.5,N,010.2,K*48",
"$GPGSA,A,3,01,02,03,04,05,06,07,08,09,10,11,12,1.2,0.8,1.0*3E"
};
for (String message : testMessages) {
Log.i(TAG, "Тестируем сообщение: " + message);
nmeaParser.parseNMEA(message);
}
}
/**
* Останавливает все слушатели
*/
public void stopAllListeners() {
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 setUDPPort(int port) {
udpListener.setPort(port);
}
/**
* Отправляет данные по 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());
// Обновляем UI через callback
if (uiUpdateCallback != null) {
uiUpdateCallback.onVesselPositionUpdated(ownVessel);
}
// Обновляем карту в главном потоке
if (mapInterface != null) {
Log.i(TAG, "Обновляем позицию на карте...");
new android.os.Handler(android.os.Looper.getMainLooper()).post(() -> {
try {
Log.i(TAG, "Вызываем mapInterface.updateOwnVesselPosition...");
mapInterface.updateOwnVesselPosition(ownVessel);
Log.i(TAG, "Позиция на карте обновлена");
} catch (Exception e) {
Log.e(TAG, "Ошибка обновления позиции на карте: " + e.getMessage(), e);
}
});
}
}
@Override
public void onGPSStatusChanged(int status) {
Log.i(TAG, "GPS статус изменился: " + status);
}
// Реализация NMEAParserListener
@Override
public void onVesselUpdated(Vessel vessel) {
// В гибридном режиме обновляем только дополнительные данные
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());
}
Log.i(TAG, "NMEA данные обновлены: course=" + vessel.getCourse() +
", speed=" + vessel.getSpeed() +
", satellites=" + vessel.getSatellites());
// Обновляем UI
if (uiUpdateCallback != null) {
uiUpdateCallback.onVesselPositionUpdated(ownVessel);
}
}
@Override
public void onDOPUpdated(double pdop, double hdop, double vdop) {
Log.i(TAG, "📊 DOP обновлен: PDOP=" + pdop + ", HDOP=" + hdop + ", VDOP=" + vdop);
// Обновляем 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) {
// Обновляем существующее судно
existingVessel.updatePosition(
vessel.getLatitude(),
vessel.getLongitude(),
vessel.getCourse(),
vessel.getSpeed()
);
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);
}
});
}
} else {
// Добавляем новое судно
aisVessels.add(vessel);
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);
}
});
}
}
// Обновляем компас с ближайшими судами
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();
new android.os.Handler(android.os.Looper.getMainLooper()).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) {
Log.d(TAG, "UDP данные получены от " + sourceAddress + ":" + sourcePort);
// Парсим полученные данные как NMEA
nmeaParser.parseNMEA(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) {
Log.i(TAG, "📱 Android NMEA сообщение получено в AppController: " + message);
// Парсим полученные данные как NMEA
nmeaParser.parseNMEA(message);
}
// Реализация 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() {
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);
}
});
}
}
/**
* Центрирует карту на позиции нашего судна
*/
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);
}
});
}
}
/**
* Освобождает ресурсы
*/
public void cleanup() {
stopAllListeners();
if (udpListener != null) {
udpListener.cleanup();
}
if (androidNmeaListener != null) {
androidNmeaListener.cleanup();
}
if (gpsLocationListener != null) {
gpsLocationListener.cleanup();
}
if (executor != null && !executor.isShutdown()) {
executor.shutdown();
}
}
}