feat: новая архитектура UI и расширенная визуализация AIS

Архитектурные улучшения:
- Внедрен UIRenderingCoordinator с централизованным throttling
- Решены проблемы зависания UI через батчинг операций карты
- Добавлен VesselPathController для отслеживания маршрутов
- Реализован MapLibreMapImpl как альтернатива Яндекс.Картам

Визуализация AIS:
- Добавлены векторные иконки для всех типов судов
- Разделение Class A/B судов с соответствующими иконками
- Иконки навигационных статусов (anchor, moored, engine, sail)
- Улучшенный CursorOverlay с информацией о судах

Производительность:
- Throttling UI обновлений (vessel: 500ms, AIS: 1s, paths: 2s)
- Устранение утечек Handler объектов
- Оптимизация GeoJSON операций в MapLibre
This commit is contained in:
2025-10-02 09:15:33 +03:00
parent 41432665ea
commit b5aee265bc
85 changed files with 7132 additions and 449 deletions
@@ -0,0 +1,128 @@
package com.grigowashere.aismap.models;
import java.io.Serializable;
/**
* Модель точки пути судна
* Содержит координаты, скорость и время прохождения
*/
public class VesselPathPoint implements Serializable {
private static final long serialVersionUID = 1L;
private double longitude;
private double latitude;
private float speed; // скорость в узлах
private long timestamp; // время в миллисекундах
public VesselPathPoint() {
this.timestamp = System.currentTimeMillis();
}
public VesselPathPoint(double longitude, double latitude, float speed) {
this.longitude = longitude;
this.latitude = latitude;
this.speed = speed;
this.timestamp = System.currentTimeMillis();
}
public VesselPathPoint(double longitude, double latitude, float speed, long timestamp) {
this.longitude = longitude;
this.latitude = latitude;
this.speed = speed;
this.timestamp = timestamp;
}
// Геттеры и сеттеры
public double getLongitude() {
return longitude;
}
public void setLongitude(double longitude) {
this.longitude = longitude;
}
public double getLatitude() {
return latitude;
}
public void setLatitude(double latitude) {
this.latitude = latitude;
}
public float getSpeed() {
return speed;
}
public void setSpeed(float speed) {
this.speed = speed;
}
public long getTimestamp() {
return timestamp;
}
public void setTimestamp(long timestamp) {
this.timestamp = timestamp;
}
/**
* Вычисляет расстояние до другой точки в метрах
*/
public double distanceTo(VesselPathPoint other) {
if (other == null) return 0;
final int R = 6371000; // радиус Земли в метрах
double lat1Rad = Math.toRadians(this.latitude);
double lat2Rad = Math.toRadians(other.latitude);
double deltaLatRad = Math.toRadians(other.latitude - this.latitude);
double deltaLonRad = Math.toRadians(other.longitude - this.longitude);
double a = Math.sin(deltaLatRad / 2) * Math.sin(deltaLatRad / 2) +
Math.cos(lat1Rad) * Math.cos(lat2Rad) *
Math.sin(deltaLonRad / 2) * Math.sin(deltaLonRad / 2);
double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
return R * c;
}
/**
* Вычисляет время между точками в секундах
*/
public long timeDifferenceSeconds(VesselPathPoint other) {
if (other == null) return 0;
return Math.abs(this.timestamp - other.timestamp) / 1000;
}
@Override
public String toString() {
return String.format("VesselPathPoint{lon=%.6f, lat=%.6f, speed=%.1f, time=%d}",
longitude, latitude, speed, timestamp);
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
VesselPathPoint that = (VesselPathPoint) obj;
return Double.compare(that.longitude, longitude) == 0 &&
Double.compare(that.latitude, latitude) == 0 &&
Float.compare(that.speed, speed) == 0 &&
timestamp == that.timestamp;
}
@Override
public int hashCode() {
int result;
long temp;
temp = Double.doubleToLongBits(longitude);
result = (int) (temp ^ (temp >>> 32));
temp = Double.doubleToLongBits(latitude);
result = 31 * result + (int) (temp ^ (temp >>> 32));
result = 31 * result + (speed != 0.0f ? Float.floatToIntBits(speed) : 0);
result = 31 * result + (int) (timestamp ^ (timestamp >>> 32));
return result;
}
}