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
@@ -10,7 +10,10 @@ import android.view.View;
import com.grigowashere.aismap.models.Vessel;
import com.grigowashere.aismap.models.AISVessel;
import com.grigowashere.aismap.view.CursorOverlay;
import com.grigowashere.aismap.R;
import com.yandex.mapkit.Animation;
import android.view.ViewGroup;
import com.yandex.mapkit.geometry.Point;
import com.yandex.mapkit.map.CameraPosition;
import com.yandex.mapkit.map.MapObjectCollection;
@@ -38,9 +41,23 @@ public class YandexMapImpl implements MapInterface {
private com.yandex.mapkit.map.InputListener inputListener;
private float lastMapAzimuth = 0.0f;
// Курсор overlay
private CursorOverlay cursorOverlay;
private Vessel ownVessel;
public YandexMapImpl(Context context, MapView mapView) {
this.context = context;
this.mapView = mapView;
this.cursorOverlay = new CursorOverlay(context);
// Добавляем overlay курсора в MapView
if (mapView instanceof ViewGroup) {
ViewGroup parent = (ViewGroup) mapView;
// Проверяем, не добавлен ли уже курсор
if (parent.findViewById(R.id.cursor_cross) == null) {
parent.addView(cursorOverlay.getView());
}
}
// Получение коллекции объектов карты
try {
@@ -59,6 +76,9 @@ public class YandexMapImpl implements MapInterface {
// Инициализируем слушатель поворота карты
setupCameraListener();
// Инициализируем слушатель движения карты
setupMapMovementListener();
// Инициализируем менеджер маркеров
if (markerManager != null) {
markerManager.initialize();
@@ -88,6 +108,10 @@ public class YandexMapImpl implements MapInterface {
@Override
public void addOwnVesselMarker(Vessel vessel) {
this.ownVessel = vessel;
if (cursorOverlay != null) {
cursorOverlay.setOwnVessel(vessel);
}
if (markerManager != null) {
markerManager.updateOwnVesselMarker(vessel);
}
@@ -95,6 +119,10 @@ public class YandexMapImpl implements MapInterface {
@Override
public void updateOwnVesselPosition(Vessel vessel) {
this.ownVessel = vessel;
if (cursorOverlay != null) {
cursorOverlay.setOwnVessel(vessel);
}
if (markerManager != null) {
markerManager.updateOwnVesselMarker(vessel);
}
@@ -255,6 +283,21 @@ public class YandexMapImpl implements MapInterface {
}
}
/**
* Очищает трекер пути собственного судна
*/
@Override
public void clearVesselPath() {
if (markerManager != null) {
markerManager.clearVesselPath("own_vessel");
}
// Также очищаем VesselPathController если он используется
// (для MapLibre это делается в MapLibreMapImpl, для Yandex - здесь)
// В YandexMapImpl VesselPathController не используется напрямую,
// но если в будущем будет использоваться, нужно добавить очистку
}
/**
* Очищает все пути движения
*/
@@ -359,4 +402,63 @@ public class YandexMapImpl implements MapInterface {
}
}
@Override
public void showCursor() {
if (cursorOverlay != null) {
cursorOverlay.showCursor();
}
}
@Override
public void hideCursor() {
if (cursorOverlay != null) {
cursorOverlay.hideCursor();
}
}
@Override
public void updateCursorCoordinates(double latitude, double longitude) {
if (cursorOverlay != null) {
cursorOverlay.updateCursorCoordinates(latitude, longitude);
}
}
@Override
public void updateCursorFromMapCenter() {
if (cursorOverlay != null && mapView != null) {
// Получаем координаты центра карты
com.yandex.mapkit.geometry.Point center = mapView.getMap().getCameraPosition().getTarget();
cursorOverlay.updateCursorCoordinates(center.getLatitude(), center.getLongitude());
}
}
@Override
public void setAisVesselInfo(com.grigowashere.aismap.models.AISVessel vessel) {
if (cursorOverlay != null) {
cursorOverlay.setAisVesselInfo(vessel);
}
}
@Override
public void clearAisVesselInfo() {
if (cursorOverlay != null) {
cursorOverlay.clearAisVesselInfo();
}
}
/**
* Настраивает слушатель движения карты для обновления курсора
*/
private void setupMapMovementListener() {
if (mapView != null) {
mapView.getMap().addCameraListener(new com.yandex.mapkit.map.CameraListener() {
@Override
public void onCameraPositionChanged(com.yandex.mapkit.map.Map map, com.yandex.mapkit.map.CameraPosition cameraPosition, com.yandex.mapkit.map.CameraUpdateReason cameraUpdateReason, boolean finished) {
// Обновляем координаты курсора при движении карты
updateCursorFromMapCenter();
}
});
}
}
}