Files
AndroidAisMap/app/src/main/java/com/grigowashere/aismap/maps/YandexMapImpl.java
T
Grigo b5aee265bc 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
2025-10-02 09:15:33 +03:00

465 lines
17 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package com.grigowashere.aismap.maps;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.drawable.Drawable;
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;
import com.yandex.mapkit.mapview.MapView;
import com.yandex.runtime.image.ImageProvider;
import java.util.HashMap;
import java.util.Map;
/**
* Реализация карты для Яндекс.Карт
* Использует новый менеджер маркеров для предотвращения финализации объектов
*/
public class YandexMapImpl implements MapInterface {
private Context context;
private MapView mapView;
private MapObjectCollection mapObjects;
private MarkerClickListener markerClickListener;
// Новый менеджер маркеров
private YandexMarkerManager markerManager;
// Слушатель поворота карты
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 {
this.mapObjects = mapView.getMap().getMapObjects().addCollection();
// Инициализируем менеджер маркеров
com.grigowashere.aismap.utils.SettingsManager settingsManager =
new com.grigowashere.aismap.utils.SettingsManager(context);
this.markerManager = new YandexMarkerManager(context, mapObjects, mapView, settingsManager);
} catch (Exception e) {
// Ошибка создания коллекции объектов карты
}
}
@Override
public void initialize() {
// Инициализируем слушатель поворота карты
setupCameraListener();
// Инициализируем слушатель движения карты
setupMapMovementListener();
// Инициализируем менеджер маркеров
if (markerManager != null) {
markerManager.initialize();
}
}
@Override
public void cleanup() {
// Очищаем менеджер маркеров
if (markerManager != null) {
markerManager.cleanup();
}
// Удаляем слушатель ввода
if (inputListener != null && mapView != null) {
mapView.getMap().removeInputListener(inputListener);
}
if (mapObjects != null) {
mapView.getMap().getMapObjects().remove(mapObjects);
}
if (mapView != null) {
mapView.onStop();
}
}
@Override
public void addOwnVesselMarker(Vessel vessel) {
this.ownVessel = vessel;
if (cursorOverlay != null) {
cursorOverlay.setOwnVessel(vessel);
}
if (markerManager != null) {
markerManager.updateOwnVesselMarker(vessel);
}
}
@Override
public void updateOwnVesselPosition(Vessel vessel) {
this.ownVessel = vessel;
if (cursorOverlay != null) {
cursorOverlay.setOwnVessel(vessel);
}
if (markerManager != null) {
markerManager.updateOwnVesselMarker(vessel);
}
}
@Override
public void addAISVesselMarker(AISVessel vessel) {
if (markerManager != null) {
markerManager.updateAISVesselMarker(vessel);
}
}
@Override
public void updateAISVesselPosition(AISVessel vessel) {
if (markerManager != null) {
markerManager.updateAISVesselMarker(vessel);
}
}
@Override
public void removeAISVesselMarker(String mmsi) {
if (markerManager != null) {
markerManager.removeAISVesselMarker(mmsi);
}
}
@Override
public void clearAISVesselMarkers() {
if (markerManager != null) {
markerManager.clearAISVesselMarkers();
}
}
@Override
public void centerOnPosition(double latitude, double longitude) {
Point point = new Point(latitude, longitude);
CameraPosition cameraPosition = new CameraPosition(point, 13.0f, 0.0f, 0.0f);
mapView.getMap().move(cameraPosition, new Animation(Animation.Type.SMOOTH, 1.0f), null);
}
@Override
public void setZoom(float zoom) {
CameraPosition currentPosition = mapView.getMap().getCameraPosition();
Point target = currentPosition.getTarget();
CameraPosition newPosition = new CameraPosition(target, zoom, currentPosition.getAzimuth(), currentPosition.getTilt());
mapView.getMap().move(newPosition, new Animation(Animation.Type.SMOOTH, 0.5f), null);
}
@Override
public float getZoom() {
return mapView.getMap().getCameraPosition().getZoom();
}
@Override
public void addLayer(String layerId, Object layerData) {
// Реализация добавления дополнительных слоев
// Зависит от конкретных требований
}
@Override
public void removeLayer(String layerId) {
// Реализация удаления слоев
}
@Override
public void setMarkerClickListener(MarkerClickListener listener) {
this.markerClickListener = listener;
// Устанавливаем обработчик в менеджере маркеров
if (markerManager != null) {
markerManager.setMarkerClickListener(listener);
}
}
/**
* Обновляет обработчики кликов для всех существующих маркеров
* Этот метод переустанавливает обработчики для всех маркеров
*/
public void refreshMarkerClickListeners() {
if (markerManager != null) {
markerManager.checkAndRestoreMarkers();
}
}
/**
* Перерисовывает все маркеры с учетом текущего азимута карты
* Вызывается при повороте карты
*/
public void refreshAllMarkers() {
if (markerManager != null) {
markerManager.refreshAllMarkers();
}
}
/**
* Обновляет все маркеры при повороте карты
* Вызывается из слушателя поворота карты
*/
public void onMapRotationChanged() {
if (markerManager != null) {
markerManager.refreshAllMarkers();
}
}
/**
* Принудительно обновляет все маркеры
* Можно вызывать извне для обновления маркеров
*/
public void forceRefreshMarkers() {
if (markerManager != null) {
markerManager.refreshAllMarkers();
}
}
/**
* Принудительно обновляет все маркеры при изменении зума
*/
public void forceRefreshMarkersOnZoomChange() {
if (markerManager != null) {
markerManager.forceRefreshAllMarkers();
}
}
/**
* Проверяет и восстанавливает финализированные маркеры
*/
public void checkAndRestoreMarkers() {
if (markerManager != null) {
markerManager.checkAndRestoreMarkers();
}
}
/**
* Получает количество активных маркеров
*/
public int getActiveMarkerCount() {
if (markerManager != null) {
return markerManager.getActiveMarkerCount();
}
return 0;
}
/**
* Включает/выключает отображение путей движения
*/
public void setPathTrackingEnabled(boolean enabled) {
if (markerManager != null) {
markerManager.setPathTrackingEnabled(enabled);
}
}
/**
* Очищает путь конкретного судна
*/
public void clearVesselPath(String mmsi) {
if (markerManager != null) {
markerManager.clearVesselPath(mmsi);
}
}
/**
* Очищает трекер пути собственного судна
*/
@Override
public void clearVesselPath() {
if (markerManager != null) {
markerManager.clearVesselPath("own_vessel");
}
// Также очищаем VesselPathController если он используется
// (для MapLibre это делается в MapLibreMapImpl, для Yandex - здесь)
// В YandexMapImpl VesselPathController не используется напрямую,
// но если в будущем будет использоваться, нужно добавить очистку
}
/**
* Очищает все пути движения
*/
public void clearAllPaths() {
if (markerManager != null) {
markerManager.clearAllPaths();
}
}
/**
* Обновляет настройки отображения путей
*/
public void updatePathSettings(int pathColor, int predictionColor, float pathWidth, float predictionWidth) {
if (markerManager != null) {
markerManager.updatePathSettings(pathColor, predictionColor, pathWidth, predictionWidth);
}
}
/**
* Получение MapView для использования в layout
*/
public MapView getMapView() {
return mapView;
}
/**
* Настройка слушателя поворота карты
*/
private void setupCameraListener() {
try {
inputListener = new com.yandex.mapkit.map.InputListener() {
@Override
public void onMapTap(com.yandex.mapkit.map.Map map, com.yandex.mapkit.geometry.Point point) {
// Не обрабатываем клики по карте
}
@Override
public void onMapLongTap(com.yandex.mapkit.map.Map map, com.yandex.mapkit.geometry.Point point) {
// Не обрабатываем долгие клики по карте
}
};
// Добавляем слушатель к карте
mapView.getMap().addInputListener(inputListener);
// Включаем жесты поворота карты
mapView.getMap().setRotateGesturesEnabled(true);
// Добавляем слушатель изменений камеры для обновления маркеров при повороте и зуме
mapView.getMap().addCameraListener(new com.yandex.mapkit.map.CameraListener() {
private long lastUpdateTime = 0;
private static final long UPDATE_THROTTLE = 200; // 200мс между обновлениями (увеличено для снижения нагрузки)
private float lastZoom = -1;
@Override
public void onCameraPositionChanged(com.yandex.mapkit.map.Map map,
com.yandex.mapkit.map.CameraPosition cameraPosition,
com.yandex.mapkit.map.CameraUpdateReason reason,
boolean finished) {
// Обновляем маркеры в реальном времени с throttling
long currentTime = System.currentTimeMillis();
float currentZoom = cameraPosition.getZoom();
// Проверяем, изменился ли зум значительно (больше чем на 1.0)
boolean zoomChanged = Math.abs(currentZoom - lastZoom) > 1.0f;
if (currentTime - lastUpdateTime >= UPDATE_THROTTLE || zoomChanged) {
//onMapRotationChanged();
// Обновляем маркеры только при значительных изменениях
if (zoomChanged) {
// При изменении зума принудительно обновляем все маркеры
forceRefreshMarkersOnZoomChange();
} else {
// При повороте только проверяем валидность маркеров
checkAndRestoreMarkers();
}
lastUpdateTime = currentTime;
lastZoom = currentZoom;
}
}
});
// Добавляем дополнительный слушатель для жестов поворота
mapView.getMap().addInputListener(new com.yandex.mapkit.map.InputListener() {
private long lastGestureTime = 0;
private static final long GESTURE_THROTTLE = 100; // 100мс между обновлениями
@Override
public void onMapTap(com.yandex.mapkit.map.Map map, com.yandex.mapkit.geometry.Point point) {
// Не обрабатываем клики по карте
}
@Override
public void onMapLongTap(com.yandex.mapkit.map.Map map, com.yandex.mapkit.geometry.Point point) {
// Не обрабатываем долгие клики по карте
}
});
} catch (Exception e) {
// Ошибка установки слушателя
}
}
@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();
}
});
}
}
}