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
465 lines
17 KiB
Java
465 lines
17 KiB
Java
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();
|
||
}
|
||
});
|
||
}
|
||
}
|
||
|
||
}
|