generated from Grigo/AndroidTemplate
Added marker manager
Hoping to get a sick day
This commit is contained in:
@@ -0,0 +1,61 @@
|
|||||||
|
package com.grigowashere.aismap.maps;
|
||||||
|
|
||||||
|
import com.grigowashere.aismap.models.Vessel;
|
||||||
|
import com.grigowashere.aismap.models.AISVessel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Интерфейс для управления маркерами на карте
|
||||||
|
* Отделяет логику управления маркерами от конкретной реализации карты
|
||||||
|
*/
|
||||||
|
public interface MarkerManager {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Инициализация менеджера маркеров
|
||||||
|
*/
|
||||||
|
void initialize();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Очистка ресурсов менеджера маркеров
|
||||||
|
*/
|
||||||
|
void cleanup();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Добавление или обновление маркера нашего судна
|
||||||
|
*/
|
||||||
|
void updateOwnVesselMarker(Vessel vessel);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Добавление или обновление маркера AIS судна
|
||||||
|
*/
|
||||||
|
void updateAISVesselMarker(AISVessel vessel);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Удаление маркера AIS судна
|
||||||
|
*/
|
||||||
|
void removeAISVesselMarker(String mmsi);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Очистка всех AIS маркеров
|
||||||
|
*/
|
||||||
|
void clearAISVesselMarkers();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Установка обработчика кликов по маркерам
|
||||||
|
*/
|
||||||
|
void setMarkerClickListener(MapInterface.MarkerClickListener listener);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Обновление всех маркеров (например, при повороте карты)
|
||||||
|
*/
|
||||||
|
void refreshAllMarkers();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Проверка и восстановление финализированных маркеров
|
||||||
|
*/
|
||||||
|
void checkAndRestoreMarkers();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Получение количества активных маркеров
|
||||||
|
*/
|
||||||
|
int getActiveMarkerCount();
|
||||||
|
}
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
package com.grigowashere.aismap.maps;
|
||||||
|
|
||||||
|
import com.grigowashere.aismap.models.Vessel;
|
||||||
|
import com.grigowashere.aismap.models.AISVessel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Обертка для маркера с управлением жизненным циклом
|
||||||
|
* Предотвращает финализацию объектов и обеспечивает стабильную работу
|
||||||
|
*/
|
||||||
|
public abstract class MarkerWrapper {
|
||||||
|
|
||||||
|
protected String id;
|
||||||
|
protected boolean isActive;
|
||||||
|
protected long lastUpdateTime;
|
||||||
|
protected long creationTime;
|
||||||
|
|
||||||
|
// Константы для управления жизненным циклом
|
||||||
|
private static final long MARKER_LIFETIME = 5000; // 5 секунд
|
||||||
|
private static final long UPDATE_THROTTLE = 200; // 0.2 секунды между обновлениями
|
||||||
|
|
||||||
|
public MarkerWrapper(String id) {
|
||||||
|
this.id = id;
|
||||||
|
this.isActive = true;
|
||||||
|
this.creationTime = System.currentTimeMillis();
|
||||||
|
this.lastUpdateTime = creationTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Проверяет, нужно ли обновлять маркер
|
||||||
|
*/
|
||||||
|
public boolean shouldUpdate() {
|
||||||
|
long currentTime = System.currentTimeMillis();
|
||||||
|
return (currentTime - lastUpdateTime) >= UPDATE_THROTTLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Проверяет, не устарел ли маркер
|
||||||
|
*/
|
||||||
|
public boolean isExpired() {
|
||||||
|
long currentTime = System.currentTimeMillis();
|
||||||
|
return (currentTime - creationTime) >= MARKER_LIFETIME;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Обновляет время последнего обновления
|
||||||
|
*/
|
||||||
|
public void markUpdated() {
|
||||||
|
this.lastUpdateTime = System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Проверяет, активен ли маркер
|
||||||
|
*/
|
||||||
|
public boolean isActive() {
|
||||||
|
return isActive && !isExpired();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Деактивирует маркер
|
||||||
|
*/
|
||||||
|
public void deactivate() {
|
||||||
|
this.isActive = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Получает ID маркера
|
||||||
|
*/
|
||||||
|
public String getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Абстрактный метод для проверки состояния маркера
|
||||||
|
*/
|
||||||
|
public abstract boolean isValid();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Абстрактный метод для обновления позиции маркера
|
||||||
|
*/
|
||||||
|
public abstract void updatePosition(double latitude, double longitude);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Абстрактный метод для обновления курса маркера
|
||||||
|
*/
|
||||||
|
public abstract void updateCourse(double course);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Абстрактный метод для удаления маркера
|
||||||
|
*/
|
||||||
|
public abstract void remove();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Абстрактный метод для обновления иконки маркера
|
||||||
|
*/
|
||||||
|
public abstract void updateIcon();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Абстрактный метод для установки обработчика кликов
|
||||||
|
*/
|
||||||
|
public abstract void setClickListener(Runnable clickHandler);
|
||||||
|
}
|
||||||
@@ -22,6 +22,7 @@ import java.util.Map;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Реализация карты для Яндекс.Карт
|
* Реализация карты для Яндекс.Карт
|
||||||
|
* Использует новый менеджер маркеров для предотвращения финализации объектов
|
||||||
*/
|
*/
|
||||||
public class YandexMapImpl implements MapInterface {
|
public class YandexMapImpl implements MapInterface {
|
||||||
|
|
||||||
@@ -30,35 +31,22 @@ public class YandexMapImpl implements MapInterface {
|
|||||||
private MapObjectCollection mapObjects;
|
private MapObjectCollection mapObjects;
|
||||||
private MarkerClickListener markerClickListener;
|
private MarkerClickListener markerClickListener;
|
||||||
|
|
||||||
private Map<String, com.yandex.mapkit.map.PlacemarkMapObject> aisMarkers;
|
// Новый менеджер маркеров
|
||||||
private Map<String, AISVessel> aisVessels; // Храним ссылки на AISVessel объекты
|
private YandexMarkerManager markerManager;
|
||||||
private com.yandex.mapkit.map.PlacemarkMapObject ownVesselMarker;
|
|
||||||
private Vessel ownVessel; // Храним ссылку на наше судно
|
|
||||||
|
|
||||||
// Флаги для отслеживания состояния обработчиков
|
|
||||||
private boolean ownVesselClickListenerSet = false;
|
|
||||||
private Map<String, Boolean> aisVesselClickListenersSet = new HashMap<>();
|
|
||||||
|
|
||||||
// Флаги для предотвращения повторного добавления обработчиков
|
|
||||||
private Map<String, Boolean> aisVesselClickListenersAdded = new HashMap<>();
|
|
||||||
|
|
||||||
// Слушатель поворота карты
|
// Слушатель поворота карты
|
||||||
private com.yandex.mapkit.map.InputListener inputListener;
|
private com.yandex.mapkit.map.InputListener inputListener;
|
||||||
private float lastMapAzimuth = 0.0f;
|
private float lastMapAzimuth = 0.0f;
|
||||||
|
|
||||||
// Отслеживание двойного клика для AIS судов
|
|
||||||
private Map<String, Long> lastClickTime = new HashMap<>();
|
|
||||||
private static final long DOUBLE_CLICK_DELAY = 300; // 300мс для двойного клика
|
|
||||||
|
|
||||||
public YandexMapImpl(Context context, MapView mapView) {
|
public YandexMapImpl(Context context, MapView mapView) {
|
||||||
this.context = context;
|
this.context = context;
|
||||||
this.mapView = mapView;
|
this.mapView = mapView;
|
||||||
this.aisMarkers = new HashMap<>();
|
|
||||||
this.aisVessels = new HashMap<>();
|
|
||||||
|
|
||||||
// Получение коллекции объектов карты
|
// Получение коллекции объектов карты
|
||||||
try {
|
try {
|
||||||
this.mapObjects = mapView.getMap().getMapObjects().addCollection();
|
this.mapObjects = mapView.getMap().getMapObjects().addCollection();
|
||||||
|
// Инициализируем менеджер маркеров
|
||||||
|
this.markerManager = new YandexMarkerManager(context, mapObjects, mapView);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
// Ошибка создания коллекции объектов карты
|
// Ошибка создания коллекции объектов карты
|
||||||
}
|
}
|
||||||
@@ -68,10 +56,21 @@ public class YandexMapImpl implements MapInterface {
|
|||||||
public void initialize() {
|
public void initialize() {
|
||||||
// Инициализируем слушатель поворота карты
|
// Инициализируем слушатель поворота карты
|
||||||
setupCameraListener();
|
setupCameraListener();
|
||||||
|
|
||||||
|
// Инициализируем менеджер маркеров
|
||||||
|
if (markerManager != null) {
|
||||||
|
markerManager.initialize();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void cleanup() {
|
public void cleanup() {
|
||||||
|
// Очищаем менеджер маркеров
|
||||||
|
if (markerManager != null) {
|
||||||
|
markerManager.cleanup();
|
||||||
|
}
|
||||||
|
|
||||||
// Удаляем слушатель ввода
|
// Удаляем слушатель ввода
|
||||||
if (inputListener != null && mapView != null) {
|
if (inputListener != null && mapView != null) {
|
||||||
mapView.getMap().removeInputListener(inputListener);
|
mapView.getMap().removeInputListener(inputListener);
|
||||||
@@ -87,168 +86,44 @@ public class YandexMapImpl implements MapInterface {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addOwnVesselMarker(Vessel vessel) {
|
public void addOwnVesselMarker(Vessel vessel) {
|
||||||
// Сохраняем ссылку на судно
|
if (markerManager != null) {
|
||||||
this.ownVessel = vessel;
|
markerManager.updateOwnVesselMarker(vessel);
|
||||||
|
|
||||||
// Проверяем валидность координат (исключаем только невалидные значения)
|
|
||||||
if (Double.isNaN(vessel.getLatitude()) || Double.isNaN(vessel.getLongitude()) ||
|
|
||||||
Double.isInfinite(vessel.getLatitude()) || Double.isInfinite(vessel.getLongitude())) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ownVesselMarker != null) {
|
|
||||||
mapObjects.remove(ownVesselMarker);
|
|
||||||
}
|
|
||||||
|
|
||||||
Point point = new Point(vessel.getLatitude(), vessel.getLongitude());
|
|
||||||
ownVesselMarker = mapObjects.addPlacemark(point);
|
|
||||||
|
|
||||||
if (ownVesselMarker == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Используем готовую иконку стрелки с учетом курса (синий цвет для нашего судна)
|
|
||||||
setMarkerIcon(ownVesselMarker, "target", vessel.getCourse(), android.graphics.Color.BLUE);
|
|
||||||
|
|
||||||
// Устанавливаем размер иконки
|
|
||||||
com.yandex.mapkit.map.IconStyle iconStyle = new com.yandex.mapkit.map.IconStyle();
|
|
||||||
iconStyle.setScale(1.0f); // Уменьшаем масштаб иконки
|
|
||||||
ownVesselMarker.setIconStyle(iconStyle);
|
|
||||||
|
|
||||||
// Устанавливаем обработчик кликов только если он еще не установлен
|
|
||||||
if (!ownVesselClickListenerSet) {
|
|
||||||
ownVesselMarker.addTapListener((mapObject, point1) -> {
|
|
||||||
if (markerClickListener != null && ownVessel != null) {
|
|
||||||
markerClickListener.onOwnVesselClick(ownVessel);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
ownVesselClickListenerSet = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateOwnVesselPosition(Vessel vessel) {
|
public void updateOwnVesselPosition(Vessel vessel) {
|
||||||
// Обновляем ссылку на судно
|
if (markerManager != null) {
|
||||||
this.ownVessel = vessel;
|
markerManager.updateOwnVesselMarker(vessel);
|
||||||
|
|
||||||
// Проверяем валидность координат (исключаем только невалидные значения)
|
|
||||||
if (Double.isNaN(vessel.getLatitude()) || Double.isNaN(vessel.getLongitude()) ||
|
|
||||||
Double.isInfinite(vessel.getLatitude()) || Double.isInfinite(vessel.getLongitude())) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ownVesselMarker == null) {
|
|
||||||
// Создаем маркер нашего судна, если его еще нет
|
|
||||||
addOwnVesselMarker(vessel);
|
|
||||||
} else {
|
|
||||||
// Всегда обновляем иконку с текущим курсом
|
|
||||||
// Обновляем иконку с новым курсом (синий цвет для нашего судна)
|
|
||||||
setMarkerIcon(ownVesselMarker, "target", vessel.getCourse(), android.graphics.Color.BLUE);
|
|
||||||
|
|
||||||
// Обновляем позицию маркера
|
|
||||||
Point newPoint = new Point(vessel.getLatitude(), vessel.getLongitude());
|
|
||||||
ownVesselMarker.setGeometry(newPoint);
|
|
||||||
|
|
||||||
// Обработчик клика уже установлен при создании маркера, не добавляем повторно
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addAISVesselMarker(AISVessel vessel) {
|
public void addAISVesselMarker(AISVessel vessel) {
|
||||||
// Проверяем валидность координат (исключаем только невалидные значения)
|
if (markerManager != null) {
|
||||||
if (Double.isNaN(vessel.getLatitude()) || Double.isNaN(vessel.getLongitude()) ||
|
markerManager.updateAISVesselMarker(vessel);
|
||||||
Double.isInfinite(vessel.getLatitude()) || Double.isInfinite(vessel.getLongitude())) {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Point point = new Point(vessel.getLatitude(), vessel.getLongitude());
|
|
||||||
com.yandex.mapkit.map.PlacemarkMapObject marker = mapObjects.addPlacemark(point);
|
|
||||||
|
|
||||||
if (marker == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Сохраняем ссылку на судно
|
|
||||||
aisVessels.put(vessel.getMmsi(), vessel);
|
|
||||||
|
|
||||||
// Используем готовую иконку стрелки для AIS судов с учетом курса и цвета
|
|
||||||
int vesselColor = getVesselColor(vessel);
|
|
||||||
setMarkerIcon(marker, "target", vessel.getCourse(), vesselColor, vessel.isSelected());
|
|
||||||
|
|
||||||
// Устанавливаем размер иконки
|
|
||||||
com.yandex.mapkit.map.IconStyle iconStyle = new com.yandex.mapkit.map.IconStyle();
|
|
||||||
iconStyle.setScale(1.0f); // Уменьшаем масштаб иконки
|
|
||||||
marker.setIconStyle(iconStyle);
|
|
||||||
|
|
||||||
// Установка обработчика кликов только если он еще не установлен
|
|
||||||
String mmsi = vessel.getMmsi();
|
|
||||||
if (!aisVesselClickListenersAdded.containsKey(mmsi) || !aisVesselClickListenersAdded.get(mmsi)) {
|
|
||||||
marker.addTapListener((mapObject, point1) -> {
|
|
||||||
// Проверяем двойной клик
|
|
||||||
long currentTime = System.currentTimeMillis();
|
|
||||||
Long lastTime = lastClickTime.get(mmsi);
|
|
||||||
|
|
||||||
if (lastTime != null && (currentTime - lastTime) < DOUBLE_CLICK_DELAY) {
|
|
||||||
// Двойной клик - вызываем BottomSheet
|
|
||||||
if (markerClickListener != null) {
|
|
||||||
markerClickListener.onAISVesselClick(vessel);
|
|
||||||
}
|
|
||||||
lastClickTime.remove(mmsi); // Сбрасываем для следующего двойного клика
|
|
||||||
} else {
|
|
||||||
// Одиночный клик - выделяем/снимаем выделение
|
|
||||||
toggleVesselSelection(vessel);
|
|
||||||
lastClickTime.put(mmsi, currentTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
aisVesselClickListenersAdded.put(mmsi, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
aisMarkers.put(vessel.getMmsi(), marker);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateAISVesselPosition(AISVessel vessel) {
|
public void updateAISVesselPosition(AISVessel vessel) {
|
||||||
// Обновляем ссылку на судно
|
if (markerManager != null) {
|
||||||
aisVessels.put(vessel.getMmsi(), vessel);
|
markerManager.updateAISVesselMarker(vessel);
|
||||||
|
|
||||||
com.yandex.mapkit.map.PlacemarkMapObject marker = aisMarkers.get(vessel.getMmsi());
|
|
||||||
if (marker != null) {
|
|
||||||
Point newPoint = new Point(vessel.getLatitude(), vessel.getLongitude());
|
|
||||||
marker.setGeometry(newPoint);
|
|
||||||
|
|
||||||
// Всегда обновляем курс маркера
|
|
||||||
int vesselColor = getVesselColor(vessel);
|
|
||||||
setMarkerIcon(marker, "target", vessel.getCourse(), vesselColor, vessel.isSelected());
|
|
||||||
|
|
||||||
// Обработчик клика уже установлен при создании маркера, не добавляем повторно
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void removeAISVesselMarker(String mmsi) {
|
public void removeAISVesselMarker(String mmsi) {
|
||||||
com.yandex.mapkit.map.PlacemarkMapObject marker = aisMarkers.remove(mmsi);
|
if (markerManager != null) {
|
||||||
if (marker != null) {
|
markerManager.removeAISVesselMarker(mmsi);
|
||||||
mapObjects.remove(marker);
|
|
||||||
}
|
}
|
||||||
// Удаляем ссылку на судно
|
|
||||||
aisVessels.remove(mmsi);
|
|
||||||
// Удаляем флаги обработчиков кликов
|
|
||||||
aisVesselClickListenersSet.remove(mmsi);
|
|
||||||
aisVesselClickListenersAdded.remove(mmsi);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void clearAISVesselMarkers() {
|
public void clearAISVesselMarkers() {
|
||||||
for (com.yandex.mapkit.map.PlacemarkMapObject marker : aisMarkers.values()) {
|
if (markerManager != null) {
|
||||||
mapObjects.remove(marker);
|
markerManager.clearAISVesselMarkers();
|
||||||
}
|
}
|
||||||
aisMarkers.clear();
|
|
||||||
aisVessels.clear();
|
|
||||||
aisVesselClickListenersSet.clear();
|
|
||||||
aisVesselClickListenersAdded.clear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -286,159 +161,62 @@ public class YandexMapImpl implements MapInterface {
|
|||||||
public void setMarkerClickListener(MarkerClickListener listener) {
|
public void setMarkerClickListener(MarkerClickListener listener) {
|
||||||
this.markerClickListener = listener;
|
this.markerClickListener = listener;
|
||||||
|
|
||||||
// Переустанавливаем обработчики кликов для всех существующих маркеров
|
// Устанавливаем обработчик в менеджере маркеров
|
||||||
updateAllMarkerClickListeners();
|
if (markerManager != null) {
|
||||||
|
markerManager.setMarkerClickListener(listener);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Обновляет обработчики кликов для всех существующих маркеров
|
* Обновляет обработчики кликов для всех существующих маркеров
|
||||||
* Этот метод переустанавливает обработчики для всех маркеров
|
* Этот метод переустанавливает обработчики для всех маркеров
|
||||||
*/
|
*/
|
||||||
private void updateAllMarkerClickListeners() {
|
public void refreshMarkerClickListeners() {
|
||||||
// Обработчик для маркера нашего судна уже установлен при создании, не добавляем повторно
|
if (markerManager != null) {
|
||||||
|
markerManager.checkAndRestoreMarkers();
|
||||||
// Обработчики для AIS маркеров уже установлены при создании, не добавляем повторно
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Создание повернутой цветной иконки из ресурса
|
|
||||||
*/
|
|
||||||
private Bitmap createRotatedIconFromResource(int resourceId, double course, int color) {
|
|
||||||
return createRotatedIconFromResource(resourceId, course, color, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Создание повернутой цветной иконки из ресурса с поддержкой выделения
|
|
||||||
*/
|
|
||||||
private Bitmap createRotatedIconFromResource(int resourceId, double course, int color, boolean isSelected) {
|
|
||||||
try {
|
|
||||||
// Получаем drawable из ресурса
|
|
||||||
Drawable drawable = context.getResources().getDrawable(resourceId, null);
|
|
||||||
if (drawable == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Применяем цвет к drawable
|
|
||||||
if (color != 0) {
|
|
||||||
drawable.setColorFilter(color, android.graphics.PorterDuff.Mode.SRC_IN);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Получаем оригинальные размеры drawable
|
|
||||||
int originalWidth = drawable.getIntrinsicWidth();
|
|
||||||
int originalHeight = drawable.getIntrinsicHeight();
|
|
||||||
|
|
||||||
// Если размеры не определены, используем стандартные
|
|
||||||
if (originalWidth <= 0) originalWidth = 32;
|
|
||||||
if (originalHeight <= 0) originalHeight = 48;
|
|
||||||
|
|
||||||
// Масштабируем размеры (уменьшаем еще больше)
|
|
||||||
float scale = 0.3f; // Уменьшаем в 3.3 раза
|
|
||||||
int width = (int) (originalWidth * scale);
|
|
||||||
int height = (int) (originalHeight * scale);
|
|
||||||
|
|
||||||
// Получаем азимут карты (поворот карты)
|
|
||||||
float mapAzimuth = 0.0f;
|
|
||||||
try {
|
|
||||||
CameraPosition cameraPosition = mapView.getMap().getCameraPosition();
|
|
||||||
mapAzimuth = cameraPosition.getAzimuth();
|
|
||||||
} catch (Exception e) {
|
|
||||||
// Не удалось получить азимут карты
|
|
||||||
}
|
|
||||||
|
|
||||||
// Создаем bitmap минимального размера для уменьшения хитбокса
|
|
||||||
int bitmapSize = Math.max(width, height) + 8; // Добавляем только небольшой отступ
|
|
||||||
Bitmap bitmap = Bitmap.createBitmap(bitmapSize, bitmapSize, Bitmap.Config.ARGB_8888);
|
|
||||||
Canvas canvas = new Canvas(bitmap);
|
|
||||||
|
|
||||||
// Поворачиваем маркер на курс судна с учетом поворота карты
|
|
||||||
// Курс судна - это направление относительно севера
|
|
||||||
// Азимут карты - это поворот карты относительно севера
|
|
||||||
// Итоговый поворот = курс судна - азимут карты (чтобы маркер оставался относительно севера)
|
|
||||||
float rotationAngle = (float) (course - mapAzimuth);
|
|
||||||
|
|
||||||
// Центрируем drawable в bitmap
|
|
||||||
int centerX = bitmapSize / 2;
|
|
||||||
int centerY = bitmapSize / 2;
|
|
||||||
int left = centerX - width / 2;
|
|
||||||
int top = centerY - height / 2;
|
|
||||||
|
|
||||||
// Устанавливаем границы для drawable
|
|
||||||
drawable.setBounds(left, top, left + width, top + height);
|
|
||||||
|
|
||||||
// Поворачиваем canvas на курс
|
|
||||||
canvas.save();
|
|
||||||
canvas.rotate(rotationAngle, centerX, centerY);
|
|
||||||
|
|
||||||
// Рисуем drawable
|
|
||||||
drawable.draw(canvas);
|
|
||||||
|
|
||||||
canvas.restore();
|
|
||||||
|
|
||||||
// Если судно выделено, добавляем рамку выделения
|
|
||||||
if (isSelected) {
|
|
||||||
// Получаем drawable для рамки выделения
|
|
||||||
Drawable selectionDrawable = context.getResources().getDrawable(
|
|
||||||
context.getResources().getIdentifier("chosentarget", "drawable", context.getPackageName()), null);
|
|
||||||
|
|
||||||
if (selectionDrawable != null) {
|
|
||||||
// Масштабируем рамку выделения
|
|
||||||
int selectionSize = Math.max(width, height) + 16; // Рамка немного больше
|
|
||||||
int selectionLeft = centerX - selectionSize / 2;
|
|
||||||
int selectionTop = centerY - selectionSize / 2;
|
|
||||||
|
|
||||||
selectionDrawable.setBounds(selectionLeft, selectionTop,
|
|
||||||
selectionLeft + selectionSize, selectionTop + selectionSize);
|
|
||||||
|
|
||||||
// Рисуем рамку выделения
|
|
||||||
selectionDrawable.draw(canvas);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return bitmap;
|
|
||||||
} catch (Exception e) {
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Создание иконки судна
|
* Перерисовывает все маркеры с учетом текущего азимута карты
|
||||||
|
* Вызывается при повороте карты
|
||||||
*/
|
*/
|
||||||
private Bitmap createVesselIcon(int color, double course) {
|
public void refreshAllMarkers() {
|
||||||
try {
|
if (markerManager != null) {
|
||||||
int size = 64; // Увеличиваем размер для лучшей видимости
|
markerManager.refreshAllMarkers();
|
||||||
Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
|
|
||||||
Canvas canvas = new Canvas(bitmap);
|
|
||||||
|
|
||||||
Paint paint = new Paint();
|
|
||||||
paint.setColor(color);
|
|
||||||
paint.setStyle(Paint.Style.FILL);
|
|
||||||
paint.setAntiAlias(true);
|
|
||||||
paint.setStrokeWidth(3.0f);
|
|
||||||
|
|
||||||
// Рисуем треугольник-стрелку, направленную вверх (по умолчанию)
|
|
||||||
android.graphics.Path path = new android.graphics.Path();
|
|
||||||
path.moveTo(size / 2f, 0); // вершина
|
|
||||||
path.lineTo(size * 0.1f, size * 0.8f); // левый нижний угол
|
|
||||||
path.lineTo(size * 0.3f, size * 0.6f); // левая внутренняя точка
|
|
||||||
path.lineTo(size * 0.3f, size * 0.9f); // левая нижняя точка
|
|
||||||
path.lineTo(size * 0.7f, size * 0.9f); // правая нижняя точка
|
|
||||||
path.lineTo(size * 0.7f, size * 0.6f); // правая внутренняя точка
|
|
||||||
path.lineTo(size * 0.9f, size * 0.8f); // правый нижний угол
|
|
||||||
path.close();
|
|
||||||
|
|
||||||
// Поворачиваем стрелку на курс (курс 0° = стрелка направлена вверх)
|
|
||||||
// В морской навигации курс 0° = север, 90° = восток, 180° = юг, 270° = запад
|
|
||||||
canvas.save();
|
|
||||||
canvas.rotate((float) course, size / 2f, size / 2f);
|
|
||||||
canvas.drawPath(path, paint);
|
|
||||||
canvas.restore();
|
|
||||||
|
|
||||||
return bitmap;
|
|
||||||
} catch (Exception e) {
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Обновляет все маркеры при повороте карты
|
||||||
|
* Вызывается из слушателя поворота карты
|
||||||
|
*/
|
||||||
|
public void onMapRotationChanged() {
|
||||||
|
if (markerManager != null) {
|
||||||
|
markerManager.refreshAllMarkers();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Проверяет и восстанавливает финализированные маркеры
|
||||||
|
*/
|
||||||
|
public void checkAndRestoreMarkers() {
|
||||||
|
if (markerManager != null) {
|
||||||
|
markerManager.checkAndRestoreMarkers();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Получает количество активных маркеров
|
||||||
|
*/
|
||||||
|
public int getActiveMarkerCount() {
|
||||||
|
if (markerManager != null) {
|
||||||
|
return markerManager.getActiveMarkerCount();
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Получение MapView для использования в layout
|
* Получение MapView для использования в layout
|
||||||
*/
|
*/
|
||||||
@@ -446,79 +224,6 @@ public class YandexMapImpl implements MapInterface {
|
|||||||
return mapView;
|
return mapView;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Принудительно пересоздает маркер нашего судна с иконкой
|
|
||||||
*/
|
|
||||||
public void recreateOwnVesselMarker(Vessel vessel) {
|
|
||||||
if (ownVesselMarker != null) {
|
|
||||||
mapObjects.remove(ownVesselMarker);
|
|
||||||
ownVesselMarker = null;
|
|
||||||
}
|
|
||||||
addOwnVesselMarker(vessel);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Обновляет обработчики кликов для всех маркеров
|
|
||||||
* Вызывается после закрытия BottomSheet для восстановления функциональности
|
|
||||||
*/
|
|
||||||
public void refreshMarkerClickListeners() {
|
|
||||||
updateAllMarkerClickListeners();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Устанавливает иконку для маркера с fallback
|
|
||||||
*/
|
|
||||||
private void setMarkerIcon(com.yandex.mapkit.map.PlacemarkMapObject marker, String iconName, double course) {
|
|
||||||
setMarkerIcon(marker, iconName, course, 0); // По умолчанию без цвета
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Устанавливает цветную иконку для маркера с fallback
|
|
||||||
*/
|
|
||||||
private void setMarkerIcon(com.yandex.mapkit.map.PlacemarkMapObject marker, String iconName, double course, int color) {
|
|
||||||
setMarkerIcon(marker, iconName, course, color, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Устанавливает цветную иконку для маркера с поддержкой выделения
|
|
||||||
*/
|
|
||||||
private void setMarkerIcon(com.yandex.mapkit.map.PlacemarkMapObject marker, String iconName, double course, int color, boolean isSelected) {
|
|
||||||
try {
|
|
||||||
// Сначала пробуем использовать ресурс с поворотом
|
|
||||||
int iconResId = context.getResources().getIdentifier(iconName, "drawable", context.getPackageName());
|
|
||||||
|
|
||||||
if (iconResId != 0) {
|
|
||||||
Bitmap rotatedBitmap = createRotatedIconFromResource(iconResId, course, color, isSelected);
|
|
||||||
if (rotatedBitmap != null) {
|
|
||||||
marker.setIcon(ImageProvider.fromBitmap(rotatedBitmap));
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
marker.setIcon(ImageProvider.fromResource(context, iconResId));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Если ресурс не найден, создаем программную иконку с учетом курса
|
|
||||||
Bitmap iconBitmap = createVesselIcon(android.graphics.Color.BLUE, course);
|
|
||||||
if (iconBitmap != null) {
|
|
||||||
marker.setIcon(ImageProvider.fromBitmap(iconBitmap));
|
|
||||||
} else {
|
|
||||||
// Создаем простую иконку как fallback
|
|
||||||
marker.setIcon(ImageProvider.fromResource(context, android.R.drawable.ic_menu_compass));
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
// Создаем простую иконку как fallback
|
|
||||||
marker.setIcon(ImageProvider.fromResource(context, android.R.drawable.ic_menu_compass));
|
|
||||||
}
|
|
||||||
|
|
||||||
// После установки иконки проверяем, что обработчик клика все еще работает
|
|
||||||
// Это может помочь с проблемами, когда установка иконки нарушает обработчики
|
|
||||||
|
|
||||||
// Обработчик клика для нашего судна уже установлен при создании маркера, не добавляем повторно
|
|
||||||
|
|
||||||
// Обработчики кликов уже установлены при создании маркеров, не добавляем повторно
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Настройка слушателя поворота карты
|
* Настройка слушателя поворота карты
|
||||||
*/
|
*/
|
||||||
@@ -541,71 +246,29 @@ public class YandexMapImpl implements MapInterface {
|
|||||||
|
|
||||||
// Включаем жесты поворота карты
|
// Включаем жесты поворота карты
|
||||||
mapView.getMap().setRotateGesturesEnabled(true);
|
mapView.getMap().setRotateGesturesEnabled(true);
|
||||||
|
|
||||||
|
// Добавляем слушатель изменений камеры для обновления маркеров при повороте
|
||||||
|
mapView.getMap().addCameraListener(new com.yandex.mapkit.map.CameraListener() {
|
||||||
|
private long lastUpdateTime = 0;
|
||||||
|
private static final long UPDATE_THROTTLE = 100; // 100мс между обновлениями
|
||||||
|
|
||||||
|
@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();
|
||||||
|
if (currentTime - lastUpdateTime >= UPDATE_THROTTLE) {
|
||||||
|
onMapRotationChanged();
|
||||||
|
lastUpdateTime = currentTime;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
// Ошибка установки слушателя
|
// Ошибка установки слушателя
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Перерисовывает все маркеры с учетом текущего азимута карты
|
|
||||||
* Вызывается при повороте карты
|
|
||||||
*/
|
|
||||||
public void refreshAllMarkers() {
|
|
||||||
// Перерисовываем маркер нашего судна (синий цвет)
|
|
||||||
if (ownVesselMarker != null && ownVessel != null) {
|
|
||||||
setMarkerIcon(ownVesselMarker, "target", ownVessel.getCourse(), android.graphics.Color.BLUE);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Перерисовываем все AIS маркеры с их цветами
|
|
||||||
for (Map.Entry<String, com.yandex.mapkit.map.PlacemarkMapObject> entry : aisMarkers.entrySet()) {
|
|
||||||
String mmsi = entry.getKey();
|
|
||||||
com.yandex.mapkit.map.PlacemarkMapObject marker = entry.getValue();
|
|
||||||
AISVessel vessel = aisVessels.get(mmsi);
|
|
||||||
|
|
||||||
if (marker != null && vessel != null) {
|
|
||||||
int vesselColor = getVesselColor(vessel);
|
|
||||||
setMarkerIcon(marker, "target", vessel.getCourse(), vesselColor, vessel.isSelected());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Переключает выделение AIS судна
|
|
||||||
*/
|
|
||||||
private void toggleVesselSelection(AISVessel vessel) {
|
|
||||||
vessel.setSelected(!vessel.isSelected());
|
|
||||||
|
|
||||||
// Обновляем иконку маркера с учетом состояния выделения
|
|
||||||
com.yandex.mapkit.map.PlacemarkMapObject marker = aisMarkers.get(vessel.getMmsi());
|
|
||||||
if (marker != null) {
|
|
||||||
int vesselColor = getVesselColor(vessel);
|
|
||||||
setMarkerIcon(marker, "target", vessel.getCourse(), vesselColor, vessel.isSelected());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Получает цвет для AIS судна в зависимости от его статуса
|
|
||||||
*/
|
|
||||||
private int getVesselColor(AISVessel vessel) {
|
|
||||||
// Можно настроить цвета в зависимости от параметров судна
|
|
||||||
// Используем navigation status из AIS данных
|
|
||||||
String navStatus = vessel.getNavigationalStatus();
|
|
||||||
if (navStatus != null) {
|
|
||||||
switch (navStatus.toLowerCase()) {
|
|
||||||
case "under way using engine":
|
|
||||||
case "under way":
|
|
||||||
return android.graphics.Color.GREEN;
|
|
||||||
case "at anchor":
|
|
||||||
return android.graphics.Color.YELLOW;
|
|
||||||
case "moored":
|
|
||||||
return android.graphics.Color.BLUE;
|
|
||||||
case "not under command":
|
|
||||||
case "restricted manoeuvrability":
|
|
||||||
return android.graphics.Color.RED;
|
|
||||||
default:
|
|
||||||
return android.graphics.Color.WHITE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return android.graphics.Color.WHITE; // По умолчанию белый
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,295 @@
|
|||||||
|
package com.grigowashere.aismap.maps;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Looper;
|
||||||
|
|
||||||
|
import com.grigowashere.aismap.models.Vessel;
|
||||||
|
import com.grigowashere.aismap.models.AISVessel;
|
||||||
|
import com.yandex.mapkit.map.MapObjectCollection;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Менеджер маркеров для Yandex Maps
|
||||||
|
* Управляет жизненным циклом маркеров и предотвращает их финализацию
|
||||||
|
*/
|
||||||
|
public class YandexMarkerManager implements MarkerManager {
|
||||||
|
|
||||||
|
private static final String TAG = "YandexMarkerManager";
|
||||||
|
|
||||||
|
private Context context;
|
||||||
|
private MapObjectCollection mapObjects;
|
||||||
|
private com.yandex.mapkit.mapview.MapView mapView;
|
||||||
|
private MapInterface.MarkerClickListener markerClickListener;
|
||||||
|
|
||||||
|
// Кеш маркеров с управлением жизненным циклом
|
||||||
|
private Map<String, YandexMarkerWrapper> markerCache = new ConcurrentHashMap<>();
|
||||||
|
private YandexMarkerWrapper ownVesselMarker;
|
||||||
|
|
||||||
|
// Периодическая очистка устаревших маркеров
|
||||||
|
private Handler cleanupHandler;
|
||||||
|
private Runnable cleanupRunnable;
|
||||||
|
private static final long CLEANUP_INTERVAL = 10000; // 10 секунд
|
||||||
|
|
||||||
|
public YandexMarkerManager(Context context, MapObjectCollection mapObjects, com.yandex.mapkit.mapview.MapView mapView) {
|
||||||
|
this.context = context;
|
||||||
|
this.mapObjects = mapObjects;
|
||||||
|
this.mapView = mapView;
|
||||||
|
this.cleanupHandler = new Handler(Looper.getMainLooper());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initialize() {
|
||||||
|
startPeriodicCleanup();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void cleanup() {
|
||||||
|
stopPeriodicCleanup();
|
||||||
|
|
||||||
|
// Удаляем все маркеры
|
||||||
|
for (YandexMarkerWrapper marker : markerCache.values()) {
|
||||||
|
marker.remove();
|
||||||
|
}
|
||||||
|
markerCache.clear();
|
||||||
|
|
||||||
|
if (ownVesselMarker != null) {
|
||||||
|
ownVesselMarker.remove();
|
||||||
|
ownVesselMarker = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateOwnVesselMarker(Vessel vessel) {
|
||||||
|
if (vessel == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Проверяем валидность координат
|
||||||
|
if (Double.isNaN(vessel.getLatitude()) || Double.isNaN(vessel.getLongitude()) ||
|
||||||
|
Double.isInfinite(vessel.getLatitude()) || Double.isInfinite(vessel.getLongitude())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ВСЕГДА пересоздаем маркер для предотвращения финализации
|
||||||
|
if (ownVesselMarker != null) {
|
||||||
|
ownVesselMarker.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Создаем новый маркер
|
||||||
|
ownVesselMarker = new YandexMarkerWrapper(context, mapObjects, mapView, vessel, "own_vessel");
|
||||||
|
if (markerClickListener != null) {
|
||||||
|
ownVesselMarker.setClickListener(() -> {
|
||||||
|
if (markerClickListener != null) {
|
||||||
|
markerClickListener.onOwnVesselClick(vessel);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateAISVesselMarker(AISVessel vessel) {
|
||||||
|
if (vessel == null || vessel.getMmsi() == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Проверяем валидность координат
|
||||||
|
if (Double.isNaN(vessel.getLatitude()) || Double.isNaN(vessel.getLongitude()) ||
|
||||||
|
Double.isInfinite(vessel.getLatitude()) || Double.isInfinite(vessel.getLongitude())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String mmsi = vessel.getMmsi();
|
||||||
|
YandexMarkerWrapper marker = markerCache.get(mmsi);
|
||||||
|
|
||||||
|
// ВСЕГДА пересоздаем маркер для предотвращения финализации
|
||||||
|
if (marker != null) {
|
||||||
|
marker.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Создаем новый маркер
|
||||||
|
marker = new YandexMarkerWrapper(context, mapObjects, mapView, vessel, mmsi);
|
||||||
|
markerCache.put(mmsi, marker);
|
||||||
|
|
||||||
|
if (markerClickListener != null) {
|
||||||
|
marker.setClickListener(() -> {
|
||||||
|
if (markerClickListener != null) {
|
||||||
|
markerClickListener.onAISVesselClick(vessel);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeAISVesselMarker(String mmsi) {
|
||||||
|
YandexMarkerWrapper marker = markerCache.remove(mmsi);
|
||||||
|
if (marker != null) {
|
||||||
|
marker.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clearAISVesselMarkers() {
|
||||||
|
for (YandexMarkerWrapper marker : markerCache.values()) {
|
||||||
|
marker.remove();
|
||||||
|
}
|
||||||
|
markerCache.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setMarkerClickListener(MapInterface.MarkerClickListener listener) {
|
||||||
|
this.markerClickListener = listener;
|
||||||
|
|
||||||
|
// Устанавливаем обработчики для существующих маркеров
|
||||||
|
if (ownVesselMarker != null && ownVesselMarker.isValid()) {
|
||||||
|
ownVesselMarker.setClickListener(() -> {
|
||||||
|
if (markerClickListener != null && ownVesselMarker.getVessel() != null) {
|
||||||
|
markerClickListener.onOwnVesselClick(ownVesselMarker.getVessel());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
for (YandexMarkerWrapper marker : markerCache.values()) {
|
||||||
|
if (marker.isValid()) {
|
||||||
|
marker.setClickListener(() -> {
|
||||||
|
if (markerClickListener != null && marker.getAISVessel() != null) {
|
||||||
|
markerClickListener.onAISVesselClick(marker.getAISVessel());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void refreshAllMarkers() {
|
||||||
|
// При повороте карты пересоздаем все маркеры
|
||||||
|
// Это гарантирует правильную ориентацию относительно севера
|
||||||
|
|
||||||
|
// Пересоздаем маркер нашего судна
|
||||||
|
if (ownVesselMarker != null) {
|
||||||
|
Vessel vessel = ownVesselMarker.getVessel();
|
||||||
|
if (vessel != null) {
|
||||||
|
ownVesselMarker.remove();
|
||||||
|
updateOwnVesselMarker(vessel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Пересоздаем все AIS маркеры
|
||||||
|
Map<String, AISVessel> vesselsToRecreate = new HashMap<>();
|
||||||
|
for (Map.Entry<String, YandexMarkerWrapper> entry : markerCache.entrySet()) {
|
||||||
|
YandexMarkerWrapper marker = entry.getValue();
|
||||||
|
AISVessel vessel = marker.getAISVessel();
|
||||||
|
if (vessel != null) {
|
||||||
|
marker.remove();
|
||||||
|
vesselsToRecreate.put(entry.getKey(), vessel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Очищаем кеш и пересоздаем маркеры
|
||||||
|
markerCache.clear();
|
||||||
|
for (Map.Entry<String, AISVessel> entry : vesselsToRecreate.entrySet()) {
|
||||||
|
updateAISVesselMarker(entry.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void checkAndRestoreMarkers() {
|
||||||
|
// Проверяем маркер нашего судна
|
||||||
|
if (ownVesselMarker != null && !ownVesselMarker.isValid()) {
|
||||||
|
Vessel vessel = ownVesselMarker.getVessel();
|
||||||
|
if (vessel != null) {
|
||||||
|
ownVesselMarker.remove();
|
||||||
|
updateOwnVesselMarker(vessel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Проверяем AIS маркеры
|
||||||
|
Set<String> toRemove = new HashSet<>();
|
||||||
|
for (Map.Entry<String, YandexMarkerWrapper> entry : markerCache.entrySet()) {
|
||||||
|
YandexMarkerWrapper marker = entry.getValue();
|
||||||
|
if (!marker.isValid()) {
|
||||||
|
AISVessel vessel = marker.getAISVessel();
|
||||||
|
if (vessel != null) {
|
||||||
|
marker.remove();
|
||||||
|
toRemove.add(entry.getKey());
|
||||||
|
updateAISVesselMarker(vessel);
|
||||||
|
} else {
|
||||||
|
toRemove.add(entry.getKey());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Удаляем невалидные маркеры
|
||||||
|
for (String mmsi : toRemove) {
|
||||||
|
markerCache.remove(mmsi);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getActiveMarkerCount() {
|
||||||
|
int count = 0;
|
||||||
|
if (ownVesselMarker != null && ownVesselMarker.isValid()) {
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
for (YandexMarkerWrapper marker : markerCache.values()) {
|
||||||
|
if (marker.isValid()) {
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Запускает периодическую очистку устаревших маркеров
|
||||||
|
*/
|
||||||
|
private void startPeriodicCleanup() {
|
||||||
|
cleanupRunnable = new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
cleanupExpiredMarkers();
|
||||||
|
cleanupHandler.postDelayed(this, CLEANUP_INTERVAL);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
cleanupHandler.post(cleanupRunnable);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Останавливает периодическую очистку
|
||||||
|
*/
|
||||||
|
private void stopPeriodicCleanup() {
|
||||||
|
if (cleanupRunnable != null) {
|
||||||
|
cleanupHandler.removeCallbacks(cleanupRunnable);
|
||||||
|
cleanupRunnable = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Очищает устаревшие маркеры
|
||||||
|
*/
|
||||||
|
private void cleanupExpiredMarkers() {
|
||||||
|
// Очищаем AIS маркеры
|
||||||
|
Set<String> toRemove = new HashSet<>();
|
||||||
|
for (Map.Entry<String, YandexMarkerWrapper> entry : markerCache.entrySet()) {
|
||||||
|
YandexMarkerWrapper marker = entry.getValue();
|
||||||
|
if (marker.isExpired() || !marker.isValid()) {
|
||||||
|
marker.remove();
|
||||||
|
toRemove.add(entry.getKey());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (String mmsi : toRemove) {
|
||||||
|
markerCache.remove(mmsi);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Проверяем маркер нашего судна
|
||||||
|
if (ownVesselMarker != null && (ownVesselMarker.isExpired() || !ownVesselMarker.isValid())) {
|
||||||
|
ownVesselMarker.remove();
|
||||||
|
ownVesselMarker = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,405 @@
|
|||||||
|
package com.grigowashere.aismap.maps;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.Paint;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
|
||||||
|
import com.grigowashere.aismap.models.Vessel;
|
||||||
|
import com.grigowashere.aismap.models.AISVessel;
|
||||||
|
import com.yandex.mapkit.geometry.Point;
|
||||||
|
import com.yandex.mapkit.map.PlacemarkMapObject;
|
||||||
|
import com.yandex.mapkit.map.MapObjectCollection;
|
||||||
|
import com.yandex.runtime.image.ImageProvider;
|
||||||
|
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Обертка для маркера Yandex Maps с управлением жизненным циклом
|
||||||
|
*/
|
||||||
|
public class YandexMarkerWrapper extends MarkerWrapper {
|
||||||
|
|
||||||
|
private Context context;
|
||||||
|
private PlacemarkMapObject marker;
|
||||||
|
private MapObjectCollection mapObjects;
|
||||||
|
private Vessel vessel;
|
||||||
|
private AISVessel aisVessel;
|
||||||
|
private boolean isOwnVessel;
|
||||||
|
private AtomicBoolean clickListenerSet = new AtomicBoolean(false);
|
||||||
|
private Runnable clickHandler;
|
||||||
|
|
||||||
|
// Ссылка на MapView для получения азимута карты
|
||||||
|
private com.yandex.mapkit.mapview.MapView mapView;
|
||||||
|
|
||||||
|
// Кешированные данные для предотвращения лишних обновлений
|
||||||
|
private double lastLatitude = Double.NaN;
|
||||||
|
private double lastLongitude = Double.NaN;
|
||||||
|
private double lastCourse = Double.NaN;
|
||||||
|
private int lastColor = -1;
|
||||||
|
private boolean lastSelected = false;
|
||||||
|
|
||||||
|
// Кеш иконок для быстрого отображения
|
||||||
|
private Bitmap cachedIconBitmap;
|
||||||
|
private double cachedIconCourse = Double.NaN;
|
||||||
|
private int cachedIconColor = -1;
|
||||||
|
private boolean cachedIconSelected = false;
|
||||||
|
|
||||||
|
public YandexMarkerWrapper(Context context, MapObjectCollection mapObjects,
|
||||||
|
com.yandex.mapkit.mapview.MapView mapView, Vessel vessel, String id) {
|
||||||
|
super(id);
|
||||||
|
this.context = context;
|
||||||
|
this.mapObjects = mapObjects;
|
||||||
|
this.mapView = mapView;
|
||||||
|
this.vessel = vessel;
|
||||||
|
this.isOwnVessel = true;
|
||||||
|
// Предварительно создаем иконку
|
||||||
|
preloadIcon();
|
||||||
|
createMarker();
|
||||||
|
}
|
||||||
|
|
||||||
|
public YandexMarkerWrapper(Context context, MapObjectCollection mapObjects,
|
||||||
|
com.yandex.mapkit.mapview.MapView mapView, AISVessel vessel, String id) {
|
||||||
|
super(id);
|
||||||
|
this.context = context;
|
||||||
|
this.mapObjects = mapObjects;
|
||||||
|
this.mapView = mapView;
|
||||||
|
this.aisVessel = vessel;
|
||||||
|
this.isOwnVessel = false;
|
||||||
|
// Предварительно создаем иконку
|
||||||
|
preloadIcon();
|
||||||
|
createMarker();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Предварительно создает иконку для быстрого отображения
|
||||||
|
*/
|
||||||
|
private void preloadIcon() {
|
||||||
|
try {
|
||||||
|
double course = isOwnVessel ? vessel.getCourse() : aisVessel.getCourse();
|
||||||
|
int color = isOwnVessel ? android.graphics.Color.BLUE : getVesselColor();
|
||||||
|
boolean selected = !isOwnVessel && aisVessel.isSelected();
|
||||||
|
|
||||||
|
cachedIconBitmap = createRotatedIcon(course, color, selected);
|
||||||
|
cachedIconCourse = course;
|
||||||
|
cachedIconColor = color;
|
||||||
|
cachedIconSelected = selected;
|
||||||
|
} catch (Exception e) {
|
||||||
|
// Ошибка предварительной загрузки иконки
|
||||||
|
cachedIconBitmap = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createMarker() {
|
||||||
|
try {
|
||||||
|
double lat = isOwnVessel ? vessel.getLatitude() : aisVessel.getLatitude();
|
||||||
|
double lon = isOwnVessel ? vessel.getLongitude() : aisVessel.getLongitude();
|
||||||
|
|
||||||
|
Point point = new Point(lat, lon);
|
||||||
|
marker = mapObjects.addPlacemark(point);
|
||||||
|
|
||||||
|
if (marker != null) {
|
||||||
|
// Сразу устанавливаем иконку, чтобы избежать плейсхолдера
|
||||||
|
setIconImmediately();
|
||||||
|
setupClickListener();
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
// Ошибка создания маркера
|
||||||
|
deactivate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Пересоздает маркер с новыми координатами
|
||||||
|
* Этот метод больше не используется - маркеры всегда пересоздаются в менеджере
|
||||||
|
*/
|
||||||
|
private void recreateMarker(double latitude, double longitude) {
|
||||||
|
// Метод оставлен для совместимости, но не используется
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Устанавливает иконку немедленно без проверок
|
||||||
|
*/
|
||||||
|
private void setIconImmediately() {
|
||||||
|
try {
|
||||||
|
double course = isOwnVessel ? vessel.getCourse() : aisVessel.getCourse();
|
||||||
|
int color = isOwnVessel ? android.graphics.Color.BLUE : getVesselColor();
|
||||||
|
boolean selected = !isOwnVessel && aisVessel.isSelected();
|
||||||
|
|
||||||
|
// Проверяем кеш иконки
|
||||||
|
Bitmap iconBitmap = null;
|
||||||
|
if (Double.compare(course, cachedIconCourse) == 0 &&
|
||||||
|
color == cachedIconColor &&
|
||||||
|
selected == cachedIconSelected &&
|
||||||
|
cachedIconBitmap != null) {
|
||||||
|
// Используем кешированную иконку
|
||||||
|
iconBitmap = cachedIconBitmap;
|
||||||
|
} else {
|
||||||
|
// Создаем новую иконку
|
||||||
|
iconBitmap = createRotatedIcon(course, color, selected);
|
||||||
|
if (iconBitmap != null) {
|
||||||
|
// Кешируем иконку
|
||||||
|
cachedIconBitmap = iconBitmap;
|
||||||
|
cachedIconCourse = course;
|
||||||
|
cachedIconColor = color;
|
||||||
|
cachedIconSelected = selected;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (iconBitmap != null) {
|
||||||
|
marker.setIcon(ImageProvider.fromBitmap(iconBitmap));
|
||||||
|
} else {
|
||||||
|
// Fallback иконка если не удалось создать повернутую
|
||||||
|
marker.setIcon(ImageProvider.fromResource(context, android.R.drawable.ic_menu_compass));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Обновляем кешированные значения
|
||||||
|
lastCourse = course;
|
||||||
|
lastColor = color;
|
||||||
|
lastSelected = selected;
|
||||||
|
} catch (Exception e) {
|
||||||
|
// Ошибка установки иконки - используем fallback
|
||||||
|
try {
|
||||||
|
marker.setIcon(ImageProvider.fromResource(context, android.R.drawable.ic_menu_compass));
|
||||||
|
} catch (Exception ex) {
|
||||||
|
// Игнорируем ошибки fallback
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isValid() {
|
||||||
|
try {
|
||||||
|
if (marker == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Пробуем получить геометрию для проверки состояния
|
||||||
|
marker.getGeometry();
|
||||||
|
return true;
|
||||||
|
} catch (Exception e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updatePosition(double latitude, double longitude) {
|
||||||
|
// Этот метод больше не используется - маркеры всегда пересоздаются
|
||||||
|
// Оставляем для совместимости с интерфейсом
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateCourse(double course) {
|
||||||
|
// Этот метод больше не используется - маркеры всегда пересоздаются
|
||||||
|
// Оставляем для совместимости с интерфейсом
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void remove() {
|
||||||
|
try {
|
||||||
|
if (marker != null) {
|
||||||
|
mapObjects.remove(marker);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
// Игнорируем ошибки при удалении
|
||||||
|
} finally {
|
||||||
|
deactivate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateIcon() {
|
||||||
|
// Этот метод больше не используется - маркеры всегда пересоздаются
|
||||||
|
// Оставляем для совместимости с интерфейсом
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setClickListener(Runnable clickHandler) {
|
||||||
|
this.clickHandler = clickHandler;
|
||||||
|
setupClickListener();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupClickListener() {
|
||||||
|
if (marker == null || clickHandler == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Сбрасываем флаг для возможности повторной установки
|
||||||
|
clickListenerSet.set(false);
|
||||||
|
|
||||||
|
try {
|
||||||
|
marker.addTapListener((mapObject, point) -> {
|
||||||
|
try {
|
||||||
|
if (mapObject != null && clickHandler != null) {
|
||||||
|
clickHandler.run();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} catch (Exception e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
clickListenerSet.set(true);
|
||||||
|
} catch (Exception e) {
|
||||||
|
// Ошибка установки обработчика кликов
|
||||||
|
clickListenerSet.set(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Bitmap createRotatedIcon(double course, int color, boolean isSelected) {
|
||||||
|
try {
|
||||||
|
// Получаем drawable из ресурса
|
||||||
|
int iconResId = context.getResources().getIdentifier("target", "drawable", context.getPackageName());
|
||||||
|
if (iconResId == 0) {
|
||||||
|
return createSimpleIcon(color, course);
|
||||||
|
}
|
||||||
|
|
||||||
|
Drawable drawable = context.getResources().getDrawable(iconResId, null);
|
||||||
|
if (drawable == null) {
|
||||||
|
return createSimpleIcon(color, course);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Применяем цвет
|
||||||
|
if (color != 0) {
|
||||||
|
drawable.setColorFilter(color, android.graphics.PorterDuff.Mode.SRC_IN);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Получаем размеры
|
||||||
|
int originalWidth = drawable.getIntrinsicWidth();
|
||||||
|
int originalHeight = drawable.getIntrinsicHeight();
|
||||||
|
|
||||||
|
if (originalWidth <= 0) originalWidth = 32;
|
||||||
|
if (originalHeight <= 0) originalHeight = 48;
|
||||||
|
|
||||||
|
// Масштабируем
|
||||||
|
float scale = 0.3f;
|
||||||
|
int width = (int) (originalWidth * scale);
|
||||||
|
int height = (int) (originalHeight * scale);
|
||||||
|
|
||||||
|
// Создаем bitmap
|
||||||
|
int bitmapSize = Math.max(width, height) + 8;
|
||||||
|
Bitmap bitmap = Bitmap.createBitmap(bitmapSize, bitmapSize, Bitmap.Config.ARGB_8888);
|
||||||
|
Canvas canvas = new Canvas(bitmap);
|
||||||
|
|
||||||
|
// Получаем азимут карты (поворот карты)
|
||||||
|
float mapAzimuth = 0.0f;
|
||||||
|
try {
|
||||||
|
if (mapView != null) {
|
||||||
|
com.yandex.mapkit.map.CameraPosition cameraPosition = mapView.getMap().getCameraPosition();
|
||||||
|
mapAzimuth = cameraPosition.getAzimuth();
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
// Не удалось получить азимут карты, используем 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Поворачиваем маркер на курс судна с учетом поворота карты
|
||||||
|
// Курс судна - это направление относительно севера
|
||||||
|
// Азимут карты - это поворот карты относительно севера
|
||||||
|
// Итоговый поворот = курс судна - азимут карты (чтобы маркер оставался относительно севера)
|
||||||
|
float rotationAngle = (float) (course - mapAzimuth);
|
||||||
|
|
||||||
|
int centerX = bitmapSize / 2;
|
||||||
|
int centerY = bitmapSize / 2;
|
||||||
|
int left = centerX - width / 2;
|
||||||
|
int top = centerY - height / 2;
|
||||||
|
|
||||||
|
drawable.setBounds(left, top, left + width, top + height);
|
||||||
|
|
||||||
|
canvas.save();
|
||||||
|
canvas.rotate(rotationAngle, centerX, centerY);
|
||||||
|
drawable.draw(canvas);
|
||||||
|
canvas.restore();
|
||||||
|
|
||||||
|
// Добавляем рамку выделения если нужно
|
||||||
|
if (isSelected) {
|
||||||
|
addSelectionFrame(canvas, centerX, centerY, Math.max(width, height));
|
||||||
|
}
|
||||||
|
|
||||||
|
return bitmap;
|
||||||
|
} catch (Exception e) {
|
||||||
|
return createSimpleIcon(color, course);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Bitmap createSimpleIcon(int color, double course) {
|
||||||
|
try {
|
||||||
|
int size = 32;
|
||||||
|
Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
|
||||||
|
Canvas canvas = new Canvas(bitmap);
|
||||||
|
|
||||||
|
Paint paint = new Paint();
|
||||||
|
paint.setColor(color);
|
||||||
|
paint.setStyle(Paint.Style.FILL);
|
||||||
|
paint.setAntiAlias(true);
|
||||||
|
|
||||||
|
// Рисуем треугольник
|
||||||
|
android.graphics.Path path = new android.graphics.Path();
|
||||||
|
path.moveTo(size / 2f, 0);
|
||||||
|
path.lineTo(size * 0.1f, size * 0.8f);
|
||||||
|
path.lineTo(size * 0.9f, size * 0.8f);
|
||||||
|
path.close();
|
||||||
|
|
||||||
|
canvas.save();
|
||||||
|
canvas.rotate((float) course, size / 2f, size / 2f);
|
||||||
|
canvas.drawPath(path, paint);
|
||||||
|
canvas.restore();
|
||||||
|
|
||||||
|
return bitmap;
|
||||||
|
} catch (Exception e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addSelectionFrame(Canvas canvas, int centerX, int centerY, int size) {
|
||||||
|
try {
|
||||||
|
int iconResId = context.getResources().getIdentifier("chosentarget", "drawable", context.getPackageName());
|
||||||
|
if (iconResId == 0) return;
|
||||||
|
|
||||||
|
Drawable selectionDrawable = context.getResources().getDrawable(iconResId, null);
|
||||||
|
if (selectionDrawable == null) return;
|
||||||
|
|
||||||
|
int selectionSize = size + 16;
|
||||||
|
int selectionLeft = centerX - selectionSize / 2;
|
||||||
|
int selectionTop = centerY - selectionSize / 2;
|
||||||
|
|
||||||
|
selectionDrawable.setBounds(selectionLeft, selectionTop,
|
||||||
|
selectionLeft + selectionSize, selectionTop + selectionSize);
|
||||||
|
selectionDrawable.draw(canvas);
|
||||||
|
} catch (Exception e) {
|
||||||
|
// Игнорируем ошибки рамки выделения
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getVesselColor() {
|
||||||
|
if (aisVessel == null) return android.graphics.Color.WHITE;
|
||||||
|
|
||||||
|
String navStatus = aisVessel.getNavigationalStatus();
|
||||||
|
if (navStatus != null) {
|
||||||
|
switch (navStatus.toLowerCase()) {
|
||||||
|
case "under way using engine":
|
||||||
|
case "under way":
|
||||||
|
return android.graphics.Color.GREEN;
|
||||||
|
case "at anchor":
|
||||||
|
return android.graphics.Color.YELLOW;
|
||||||
|
case "moored":
|
||||||
|
return android.graphics.Color.BLUE;
|
||||||
|
case "not under command":
|
||||||
|
case "restricted manoeuvrability":
|
||||||
|
return android.graphics.Color.RED;
|
||||||
|
default:
|
||||||
|
return android.graphics.Color.WHITE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return android.graphics.Color.WHITE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vessel getVessel() {
|
||||||
|
return vessel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AISVessel getAISVessel() {
|
||||||
|
return aisVessel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isOwnVessel() {
|
||||||
|
return isOwnVessel;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user