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,533 @@
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.yandex.mapkit.Animation;
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 Map<String, com.yandex.mapkit.map.PlacemarkMapObject> aisMarkers;
private Map<String, AISVessel> aisVessels; // Храним ссылки на AISVessel объекты
private com.yandex.mapkit.map.PlacemarkMapObject ownVesselMarker;
private Vessel ownVessel; // Храним ссылку на наше судно
// Флаги для отслеживания состояния обработчиков
private boolean ownVesselClickListenerSet = false;
private Map<String, Boolean> aisVesselClickListenersSet = new HashMap<>();
public YandexMapImpl(Context context, MapView mapView) {
this.context = context;
this.mapView = mapView;
this.aisMarkers = new HashMap<>();
this.aisVessels = new HashMap<>();
android.util.Log.d("YandexMapImpl", "Конструктор YandexMapImpl вызван");
android.util.Log.d("YandexMapImpl", "Context: " + (context != null ? "установлен" : "null"));
android.util.Log.d("YandexMapImpl", "MapView: " + (mapView != null ? "установлен" : "null"));
// Получение коллекции объектов карты
try {
this.mapObjects = mapView.getMap().getMapObjects().addCollection();
android.util.Log.d("YandexMapImpl", "Коллекция объектов карты создана: " + (mapObjects != null ? "успешно" : "null"));
} catch (Exception e) {
android.util.Log.e("YandexMapImpl", "Ошибка создания коллекции объектов карты: " + e.getMessage(), e);
}
}
@Override
public void initialize() {
android.util.Log.d("YandexMapImpl", "initialize() вызван");
android.util.Log.d("YandexMapImpl", "mapObjects: " + (mapObjects != null ? "установлен" : "null"));
android.util.Log.d("YandexMapImpl", "mapView: " + (mapView != null ? "установлен" : "null"));
android.util.Log.d("YandexMapImpl", "context: " + (context != null ? "установлен" : "null"));
// Карта уже инициализирована в конструкторе
if (mapObjects != null) {
android.util.Log.d("YandexMapImpl", "Коллекция объектов карты готова к использованию");
}
}
@Override
public void cleanup() {
if (mapObjects != null) {
mapView.getMap().getMapObjects().remove(mapObjects);
}
if (mapView != null) {
mapView.onStop();
}
}
@Override
public void addOwnVesselMarker(Vessel vessel) {
android.util.Log.d("YandexMapImpl", "addOwnVesselMarker вызван: lat=" + vessel.getLatitude() + ", lon=" + vessel.getLongitude() + ", course=" + vessel.getCourse() + "°");
// Сохраняем ссылку на судно
this.ownVessel = vessel;
// Проверяем координаты
if (vessel.getLatitude() == 0.0 && vessel.getLongitude() == 0.0) {
android.util.Log.w("YandexMapImpl", "Координаты равны 0,0 - маркер не будет создан");
return;
}
if (ownVesselMarker != null) {
android.util.Log.d("YandexMapImpl", "Удаляем существующий маркер");
mapObjects.remove(ownVesselMarker);
}
Point point = new Point(vessel.getLatitude(), vessel.getLongitude());
android.util.Log.d("YandexMapImpl", "Создаем Point: " + point);
ownVesselMarker = mapObjects.addPlacemark(point);
android.util.Log.d("YandexMapImpl", "Placemark создан: " + (ownVesselMarker != null ? "успешно" : "null"));
if (ownVesselMarker == null) {
android.util.Log.e("YandexMapImpl", "Не удалось создать Placemark!");
return;
}
// Используем готовую иконку стрелки с учетом курса
android.util.Log.d("YandexMapImpl", "Устанавливаем иконку стрелки с курсом: " + vessel.getCourse() + "°");
setMarkerIcon(ownVesselMarker, "arrowship", vessel.getCourse());
// Устанавливаем размер иконки
android.util.Log.d("YandexMapImpl", "Устанавливаем IconStyle...");
com.yandex.mapkit.map.IconStyle iconStyle = new com.yandex.mapkit.map.IconStyle();
iconStyle.setScale(1.5f); // Увеличиваем размер иконки
ownVesselMarker.setIconStyle(iconStyle);
// Устанавливаем обработчик кликов только если он еще не установлен
if (!ownVesselClickListenerSet) {
android.util.Log.d("YandexMapImpl", "Устанавливаем обработчик клика для маркера...");
ownVesselMarker.addTapListener((mapObject, point1) -> {
android.util.Log.d("YandexMapImpl", "Клик по маркеру нашего судна!");
if (markerClickListener != null && ownVessel != null) {
android.util.Log.d("YandexMapImpl", "Вызываем callback onOwnVesselClick");
markerClickListener.onOwnVesselClick(ownVessel);
} else {
android.util.Log.e("YandexMapImpl", "markerClickListener == null или ownVessel == null!");
android.util.Log.d("YandexMapImpl", "markerClickListener = " + (markerClickListener != null ? "установлен" : "null"));
android.util.Log.d("YandexMapImpl", "ownVessel = " + (ownVessel != null ? "установлен" : "null"));
}
return true;
});
ownVesselClickListenerSet = true;
}
android.util.Log.d("YandexMapImpl", "Маркер нашего судна создан и настроен, markerClickListener = " + (markerClickListener != null ? "установлен" : "null"));
// Проверяем, что маркер действительно добавлен в коллекцию
android.util.Log.d("YandexMapImpl", "Маркер добавлен в коллекцию объектов карты");
}
@Override
public void updateOwnVesselPosition(Vessel vessel) {
android.util.Log.d("YandexMapImpl", "updateOwnVesselPosition вызван: lat=" + vessel.getLatitude() + ", lon=" + vessel.getLongitude() + ", course=" + vessel.getCourse() + "°");
// Обновляем ссылку на судно
this.ownVessel = vessel;
// Проверяем координаты
if (vessel.getLatitude() == 0.0 && vessel.getLongitude() == 0.0) {
android.util.Log.w("YandexMapImpl", "Координаты равны 0,0 - обновление пропущено");
return;
}
if (ownVesselMarker == null) {
// Создаем маркер нашего судна, если его еще нет
android.util.Log.d("YandexMapImpl", "Создаем новый маркер нашего судна");
addOwnVesselMarker(vessel);
} else {
// Проверяем, нужно ли обновить курс
boolean needCourseUpdate = Math.abs(vessel.getCourse()) > 0.1; // Если курс больше 0.1 градуса
if (needCourseUpdate) {
android.util.Log.d("YandexMapImpl", "Обновляем курс маркера на " + vessel.getCourse() + "°");
// Обновляем только иконку с новым курсом
setMarkerIcon(ownVesselMarker, "arrowship", vessel.getCourse());
}
// Обновляем позицию маркера
Point newPoint = new Point(vessel.getLatitude(), vessel.getLongitude());
ownVesselMarker.setGeometry(newPoint);
android.util.Log.d("YandexMapImpl", "Позиция маркера обновлена на: " + newPoint);
// Переустанавливаем обработчик клика после обновления маркера
if (markerClickListener != null) {
android.util.Log.d("YandexMapImpl", "Переустанавливаем обработчик клика после обновления маркера");
// В Яндекс.Картах нет метода setTapListener(null), поэтому просто добавляем новый обработчик
ownVesselMarker.addTapListener((mapObject, point1) -> {
android.util.Log.d("YandexMapImpl", "Клик по маркеру нашего судна!");
if (markerClickListener != null && ownVessel != null) {
android.util.Log.d("YandexMapImpl", "Вызываем callback onOwnVesselClick");
markerClickListener.onOwnVesselClick(ownVessel);
} else {
android.util.Log.e("YandexMapImpl", "markerClickListener == null или ownVessel == null!");
}
return true;
});
}
}
android.util.Log.d("YandexMapImpl", "Маркер нашего судна обновлен, ownVesselMarker = " + (ownVesselMarker != null ? "создан" : "null") + ", markerClickListener = " + (markerClickListener != null ? "установлен" : "null"));
}
@Override
public void addAISVesselMarker(AISVessel vessel) {
android.util.Log.d("YandexMapImpl", "addAISVesselMarker вызван: lat=" + vessel.getLatitude() + ", lon=" + vessel.getLongitude() + ", course=" + vessel.getCourse() + "°");
Point point = new Point(vessel.getLatitude(), vessel.getLongitude());
com.yandex.mapkit.map.PlacemarkMapObject marker = mapObjects.addPlacemark(point);
// Сохраняем ссылку на судно
aisVessels.put(vessel.getMmsi(), vessel);
// Используем готовую иконку стрелки для AIS судов с учетом курса
setMarkerIcon(marker, "arrowship", vessel.getCourse());
// Устанавливаем размер иконки
com.yandex.mapkit.map.IconStyle iconStyle = new com.yandex.mapkit.map.IconStyle();
iconStyle.setScale(1.5f); // Увеличиваем размер иконки
marker.setIconStyle(iconStyle);
// Установка обработчика кликов только если он еще не установлен
String mmsi = vessel.getMmsi();
if (!aisVesselClickListenersSet.containsKey(mmsi) || !aisVesselClickListenersSet.get(mmsi)) {
marker.addTapListener((mapObject, point1) -> {
android.util.Log.d("YandexMapImpl", "Клик по AIS маркеру: " + mmsi);
if (markerClickListener != null) {
android.util.Log.d("YandexMapImpl", "Вызываем callback onAISVesselClick");
markerClickListener.onAISVesselClick(vessel);
} else {
android.util.Log.e("YandexMapImpl", "markerClickListener == null!");
android.util.Log.d("YandexMapImpl", "markerClickListener = " + (markerClickListener != null ? "установлен" : "null"));
}
return true;
});
aisVesselClickListenersSet.put(mmsi, true);
}
aisMarkers.put(vessel.getMmsi(), marker);
}
@Override
public void updateAISVesselPosition(AISVessel vessel) {
// Обновляем ссылку на судно
aisVessels.put(vessel.getMmsi(), 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);
// Обновляем курс маркера, если он изменился
if (Math.abs(vessel.getCourse()) > 0.1) {
android.util.Log.d("YandexMapImpl", "Обновляем курс AIS маркера " + vessel.getMmsi() + " на " + vessel.getCourse() + "°");
setMarkerIcon(marker, "arrowship", vessel.getCourse());
}
// Переустанавливаем обработчик клика после обновления маркера
if (markerClickListener != null) {
android.util.Log.d("YandexMapImpl", "Переустанавливаем обработчик клика для AIS маркера: " + vessel.getMmsi());
// В Яндекс.Картах нет метода setTapListener(null), поэтому просто добавляем новый обработчик
marker.addTapListener((mapObject, point1) -> {
android.util.Log.d("YandexMapImpl", "Клик по AIS маркеру: " + vessel.getMmsi());
if (markerClickListener != null) {
android.util.Log.d("YandexMapImpl", "Вызываем callback onAISVesselClick");
markerClickListener.onAISVesselClick(vessel);
} else {
android.util.Log.e("YandexMapImpl", "markerClickListener == null!");
}
return true;
});
}
}
}
@Override
public void removeAISVesselMarker(String mmsi) {
com.yandex.mapkit.map.PlacemarkMapObject marker = aisMarkers.remove(mmsi);
if (marker != null) {
mapObjects.remove(marker);
}
// Удаляем ссылку на судно
aisVessels.remove(mmsi);
// Удаляем флаг обработчика кликов
aisVesselClickListenersSet.remove(mmsi);
}
@Override
public void clearAISVesselMarkers() {
for (com.yandex.mapkit.map.PlacemarkMapObject marker : aisMarkers.values()) {
mapObjects.remove(marker);
}
aisMarkers.clear();
aisVessels.clear();
aisVesselClickListenersSet.clear();
}
@Override
public void centerOnPosition(double latitude, double longitude) {
Point point = new Point(latitude, longitude);
CameraPosition cameraPosition = new CameraPosition(point, 15.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) {
android.util.Log.d("YandexMapImpl", "setMarkerClickListener вызван: " + (listener != null ? "listener установлен" : "listener == null"));
this.markerClickListener = listener;
// Переустанавливаем обработчики кликов для всех существующих маркеров
updateAllMarkerClickListeners();
}
/**
* Обновляет обработчики кликов для всех существующих маркеров
* Этот метод переустанавливает обработчики для всех маркеров
*/
private void updateAllMarkerClickListeners() {
android.util.Log.d("YandexMapImpl", "updateAllMarkerClickListeners вызван - переустанавливаем обработчики");
// Переустанавливаем обработчик для маркера нашего судна
if (ownVesselMarker != null) {
android.util.Log.d("YandexMapImpl", "Переустанавливаем обработчик для маркера нашего судна");
// В Яндекс.Картах нет метода setTapListener(null), поэтому просто добавляем новый обработчик
ownVesselMarker.addTapListener((mapObject, point1) -> {
android.util.Log.d("YandexMapImpl", "Клик по маркеру нашего судна!");
if (markerClickListener != null && ownVessel != null) {
android.util.Log.d("YandexMapImpl", "Вызываем callback onOwnVesselClick");
markerClickListener.onOwnVesselClick(ownVessel);
} else {
android.util.Log.e("YandexMapImpl", "markerClickListener == null или ownVessel == null!");
}
return true;
});
ownVesselClickListenerSet = true;
}
// Переустанавливаем обработчики для 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) {
android.util.Log.d("YandexMapImpl", "Переустанавливаем обработчик для AIS маркера: " + mmsi);
// В Яндекс.Картах нет метода setTapListener(null), поэтому просто добавляем новый обработчик
marker.addTapListener((mapObject, point1) -> {
android.util.Log.d("YandexMapImpl", "Клик по AIS маркеру: " + mmsi);
if (markerClickListener != null) {
android.util.Log.d("YandexMapImpl", "Вызываем callback onAISVesselClick");
markerClickListener.onAISVesselClick(vessel);
} else {
android.util.Log.e("YandexMapImpl", "markerClickListener == null!");
}
return true;
});
aisVesselClickListenersSet.put(mmsi, true);
}
}
}
/**
* Создание иконки судна
*/
private Bitmap createVesselIcon(int color, double course) {
try {
int size = 64; // Увеличиваем размер для лучшей видимости
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();
android.util.Log.d("YandexMapImpl", "Программная иконка с курсом " + course + "° создана успешно, размер: " + size + "x" + size);
return bitmap;
} catch (Exception e) {
android.util.Log.e("YandexMapImpl", "Ошибка создания программной иконки: " + e.getMessage(), e);
return null;
}
}
/**
* Получение MapView для использования в layout
*/
public MapView getMapView() {
return mapView;
}
/**
* Принудительно пересоздает маркер нашего судна с иконкой
*/
public void recreateOwnVesselMarker(Vessel vessel) {
android.util.Log.d("YandexMapImpl", "Принудительно пересоздаем маркер нашего судна");
if (ownVesselMarker != null) {
mapObjects.remove(ownVesselMarker);
ownVesselMarker = null;
}
addOwnVesselMarker(vessel);
}
/**
* Обновляет обработчики кликов для всех маркеров
* Вызывается после закрытия BottomSheet для восстановления функциональности
*/
public void refreshMarkerClickListeners() {
android.util.Log.d("YandexMapImpl", "refreshMarkerClickListeners вызван - переустанавливаем все обработчики");
updateAllMarkerClickListeners();
}
/**
* Устанавливает иконку для маркера с fallback
*/
private void setMarkerIcon(com.yandex.mapkit.map.PlacemarkMapObject marker, String iconName, double course) {
try {
android.util.Log.d("YandexMapImpl", "Пытаемся установить иконку: " + iconName + " с курсом: " + course + "°");
android.util.Log.d("YandexMapImpl", "Package name: " + context.getPackageName());
// Сначала пробуем создать программную иконку с учетом курса
android.util.Log.d("YandexMapImpl", "Создаем программную иконку стрелки с курсом " + course + "°...");
Bitmap iconBitmap = createVesselIcon(android.graphics.Color.BLUE, course);
if (iconBitmap != null) {
android.util.Log.d("YandexMapImpl", "Программная иконка с курсом " + course + "° создана, устанавливаем...");
marker.setIcon(ImageProvider.fromBitmap(iconBitmap));
android.util.Log.d("YandexMapImpl", "Программная иконка с курсом " + course + "° установлена успешно");
return;
}
// Если программная иконка не создалась, пробуем ресурс
int iconResId = context.getResources().getIdentifier(iconName, "drawable", context.getPackageName());
android.util.Log.d("YandexMapImpl", "ID ресурса " + iconName + ": " + iconResId);
if (iconResId != 0) {
android.util.Log.d("YandexMapImpl", "Устанавливаем иконку из ресурса...");
marker.setIcon(ImageProvider.fromResource(context, iconResId));
android.util.Log.d("YandexMapImpl", "Иконка " + iconName + " установлена успешно");
} else {
android.util.Log.e("YandexMapImpl", "Не удалось найти ресурс " + iconName);
android.util.Log.d("YandexMapImpl", "Используем fallback иконку...");
// Создаем простую иконку как fallback
marker.setIcon(ImageProvider.fromResource(context, android.R.drawable.ic_menu_compass));
android.util.Log.d("YandexMapImpl", "Fallback иконка установлена");
}
} catch (Exception e) {
android.util.Log.e("YandexMapImpl", "Ошибка установки иконки " + iconName + ": " + e.getMessage(), e);
android.util.Log.d("YandexMapImpl", "Используем fallback иконку после ошибки...");
// Создаем простую иконку как fallback
marker.setIcon(ImageProvider.fromResource(context, android.R.drawable.ic_menu_compass));
android.util.Log.d("YandexMapImpl", "Fallback иконка установлена после ошибки");
}
// После установки иконки проверяем, что обработчик клика все еще работает
// Это может помочь с проблемами, когда установка иконки нарушает обработчики
android.util.Log.d("YandexMapImpl", "Иконка установлена, проверяем обработчик клика...");
// Дополнительная проверка: если это маркер нашего судна, переустанавливаем обработчик клика
if (marker == ownVesselMarker && markerClickListener != null) {
android.util.Log.d("YandexMapImpl", "Переустанавливаем обработчик клика для маркера нашего судна после установки иконки");
// В Яндекс.Картах нет метода setTapListener(null), поэтому просто добавляем новый обработчик
marker.addTapListener((mapObject, point1) -> {
android.util.Log.d("YandexMapImpl", "Клик по маркеру нашего судна!");
if (markerClickListener != null && ownVessel != null) {
android.util.Log.d("YandexMapImpl", "Вызываем callback onOwnVesselClick");
markerClickListener.onOwnVesselClick(ownVessel);
} else {
android.util.Log.e("YandexMapImpl", "markerClickListener == null или ownVessel == null!");
}
return true;
});
}
// Дополнительная проверка: если это AIS маркер, переустанавливаем обработчик клика
for (Map.Entry<String, com.yandex.mapkit.map.PlacemarkMapObject> entry : aisMarkers.entrySet()) {
if (entry.getValue() == marker && markerClickListener != null) {
String mmsi = entry.getKey();
AISVessel vessel = aisVessels.get(mmsi);
if (vessel != null) {
android.util.Log.d("YandexMapImpl", "Переустанавливаем обработчик клика для AIS маркера " + mmsi + " после установки иконки");
// В Яндекс.Картах нет метода setTapListener(null), поэтому просто добавляем новый обработчик
marker.addTapListener((mapObject, point1) -> {
android.util.Log.d("YandexMapImpl", "Клик по AIS маркеру: " + mmsi);
if (markerClickListener != null) {
android.util.Log.d("YandexMapImpl", "Вызываем callback onAISVesselClick");
markerClickListener.onAISVesselClick(vessel);
} else {
android.util.Log.e("YandexMapImpl", "markerClickListener == null!");
}
return true;
});
}
break;
}
}
}
}