diff --git a/static/css/ais_dashboard.css b/static/css/ais_dashboard.css
index 068350c..d564cf8 100644
--- a/static/css/ais_dashboard.css
+++ b/static/css/ais_dashboard.css
@@ -495,9 +495,8 @@ body {
}
.no-vessel-icon {
- font-size: 36px;
margin-bottom: 10px;
- opacity: 0.5;
+ color: var(--accent-cyan);
}
.vessel-header {
@@ -508,7 +507,9 @@ body {
}
.vessel-icon {
- font-size: 32px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
}
.vessel-name {
@@ -595,7 +596,10 @@ body {
}
.vessel-item-icon {
- font-size: 20px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 24px;
}
.vessel-item-info {
@@ -747,19 +751,12 @@ body {
border: none;
}
-.vessel-marker-inner {
- width: 24px;
- height: 24px;
- display: flex;
- align-items: center;
- justify-content: center;
- font-size: 18px;
- filter: drop-shadow(0 0 2px rgba(0,0,0,0.8));
- transition: transform 0.3s ease;
+.vessel-marker svg {
+ transition: filter 0.2s ease;
}
-.vessel-marker.selected .vessel-marker-inner {
- filter: drop-shadow(0 0 6px var(--accent-cyan));
+.vessel-marker.selected svg {
+ filter: drop-shadow(0 0 8px rgba(255,255,255,0.8)) !important;
}
/* Range rings */
diff --git a/templates/ais_dashboard.html b/templates/ais_dashboard.html
index 63014ea..1a90afa 100644
--- a/templates/ais_dashboard.html
+++ b/templates/ais_dashboard.html
@@ -78,7 +78,11 @@
@@ -204,34 +208,48 @@
let messageRateInterval = null;
let lastMessageCount = 0;
- // Ship type to icon mapping
- const SHIP_ICONS = {
- 30: '🐟', // Fishing
- 31: '🚢', // Towing
- 32: '🚢', // Towing
- 36: '⛵', // Sailing
- 37: '⛵', // Pleasure craft
- 60: '🚢', // Passenger
- 61: '🚢', // Passenger
- 62: '🚢', // Passenger
- 63: '🚢', // Passenger
- 64: '🚢', // Passenger
- 65: '🚢', // Passenger
- 66: '🚢', // Passenger
- 67: '🚢', // Passenger
- 68: '🚢', // Passenger
- 69: '🚢', // Passenger
- 70: '🚢', // Cargo
- 71: '🚢', // Cargo - hazardous A
- 72: '🚢', // Cargo - hazardous B
- 73: '🚢', // Cargo - hazardous C
- 74: '🚢', // Cargo - hazardous D
- 80: '🚢', // Tanker
- 81: '🚢', // Tanker - hazardous A
- 82: '🚢', // Tanker - hazardous B
- 83: '🚢', // Tanker - hazardous C
- 84: '🚢', // Tanker - hazardous D
- default: '🚢' // Generic ship
+ // Vessel SVG icon paths (top-down view, pointing up)
+ const VESSEL_ICONS = {
+ // Generic cargo/container ship - pointed bow, rectangular hull
+ cargo: 'M12 2L8 6V18L10 20H14L16 18V6L12 2ZM10 8H14V16H10V8Z',
+ // Tanker - rounded bow, long hull
+ tanker: 'M12 2C10 2 8 4 8 6V18C8 19 9 20 10 20H14C15 20 16 19 16 18V6C16 4 14 2 12 2ZM10 8H14V16H10V8Z',
+ // Passenger/cruise - multiple decks indicated
+ passenger: 'M12 2L8 5V18L10 20H14L16 18V5L12 2ZM9 7H15V10H9V7ZM9 11H15V14H9V11ZM9 15H15V18H9V15Z',
+ // Tug - small, compact, powerful
+ tug: 'M12 4L9 7V16L10 18H14L15 16V7L12 4ZM10 9H14V14H10V9Z',
+ // Fishing vessel - with mast/outriggers
+ fishing: 'M12 2L12 5L8 8V17L10 19H14L16 17V8L12 5ZM6 10L8 12V15L6 13V10ZM18 10V13L16 15V12L18 10ZM10 10H14V15H10V10Z',
+ // Sailing vessel - sail shape
+ sailing: 'M12 2L12 6L8 10V18L10 20H14L16 18V10L12 6ZM12 3L16 8H12V3ZM10 11H14V17H10V11Z',
+ // Military - angular, aggressive bow
+ military: 'M12 1L7 6V8L8 9V18L10 20H14L16 18V9L17 8V6L12 1ZM10 10H14V16H10V10Z',
+ // High speed craft - sleek, pointed
+ hsc: 'M12 1L9 5V18L10 20H14L15 18V5L12 1ZM10 7H14V17H10V7Z',
+ // Search & rescue - distinctive cross marking
+ sar: 'M12 2L8 6V18L10 20H14L16 18V6L12 2ZM11 8H13V11H16V13H13V16H11V13H8V11H11V8Z',
+ // Pilot vessel
+ pilot: 'M12 3L9 6V17L10 19H14L15 17V6L12 3ZM10 8H14V15H10V8ZM11 9V10H13V9H11Z',
+ // Law enforcement
+ law: 'M12 2L8 6V18L10 20H14L16 18V6L12 2ZM10 8H14V10H10V8ZM11 11H13V16H11V11Z',
+ // Generic vessel (default)
+ default: 'M12 2L8 6V18L10 20H14L16 18V6L12 2Z'
+ };
+
+ // Vessel type colors
+ const VESSEL_COLORS = {
+ cargo: '#00d4ff', // Cyan
+ tanker: '#ff6b35', // Orange
+ passenger: '#a855f7', // Purple
+ tug: '#fbbf24', // Yellow
+ fishing: '#22c55e', // Green
+ sailing: '#60a5fa', // Light blue
+ military: '#ef4444', // Red
+ hsc: '#f472b6', // Pink
+ sar: '#ff0000', // Bright red
+ pilot: '#ffffff', // White
+ law: '#3b82f6', // Blue
+ default: '#00d4ff' // Cyan
};
// Ship type categories
@@ -255,8 +273,50 @@
return 'Other';
}
- function getShipIcon(type) {
- return SHIP_ICONS[type] || SHIP_ICONS.default;
+ // Get vessel icon type from AIS ship type code
+ function getVesselIconType(type) {
+ if (!type) return 'default';
+ if (type === 30) return 'fishing';
+ if (type >= 31 && type <= 32) return 'tug';
+ if (type === 35) return 'military';
+ if (type >= 36 && type <= 37) return 'sailing';
+ if (type >= 40 && type < 50) return 'hsc';
+ if (type === 50) return 'pilot';
+ if (type === 51) return 'sar';
+ if (type === 52) return 'tug';
+ if (type === 55) return 'law';
+ if (type >= 60 && type < 70) return 'passenger';
+ if (type >= 70 && type < 80) return 'cargo';
+ if (type >= 80 && type < 90) return 'tanker';
+ return 'default';
+ }
+
+ // Create SVG vessel marker icon
+ function createVesselMarkerIcon(rotation, vesselType, isSelected = false) {
+ const path = VESSEL_ICONS[vesselType] || VESSEL_ICONS.default;
+ const color = VESSEL_COLORS[vesselType] || VESSEL_COLORS.default;
+ const size = 24;
+ const glowColor = isSelected ? 'rgba(255,255,255,0.8)' : color;
+ const glowSize = isSelected ? '8px' : '4px';
+
+ return L.divIcon({
+ className: 'vessel-marker' + (isSelected ? ' selected' : ''),
+ html: ``,
+ iconSize: [size, size],
+ iconAnchor: [size/2, size/2]
+ });
+ }
+
+ // Legacy function for vessel list icons (returns SVG string)
+ function getShipIconSvg(type, size = 18) {
+ const vesselType = getVesselIconType(type);
+ const path = VESSEL_ICONS[vesselType] || VESSEL_ICONS.default;
+ const color = VESSEL_COLORS[vesselType] || VESSEL_COLORS.default;
+ return ``;
}
// Navigation status text
@@ -544,20 +604,9 @@
if (!vessel.lat || !vessel.lon) return;
const heading = vessel.heading || vessel.course || 0;
- const icon = getShipIcon(vessel.ship_type);
-
- const markerHtml = `
-
- ${icon}
-
- `;
-
- const divIcon = L.divIcon({
- className: 'vessel-marker' + (mmsi === selectedMmsi ? ' selected' : ''),
- html: markerHtml,
- iconSize: [24, 24],
- iconAnchor: [12, 12]
- });
+ const vesselType = getVesselIconType(vessel.ship_type);
+ const isSelected = mmsi === selectedMmsi;
+ const divIcon = createVesselMarkerIcon(heading, vesselType, isSelected);
if (markers[mmsi]) {
markers[mmsi].setLatLng([vessel.lat, vessel.lon]);
@@ -573,13 +622,17 @@
}
function selectVessel(mmsi) {
+ const prevSelected = selectedMmsi;
selectedMmsi = mmsi;
- // Update marker styles
- Object.keys(markers).forEach(m => {
- const el = markers[m].getElement();
- if (el) {
- el.querySelector('.vessel-marker-inner')?.parentElement?.classList.toggle('selected', m === mmsi);
+ // Update marker icons for previous and new selection
+ [prevSelected, mmsi].forEach(m => {
+ if (m && vessels[m] && markers[m]) {
+ const vessel = vessels[m];
+ const heading = vessel.heading || vessel.course || 0;
+ const vesselType = getVesselIconType(vessel.ship_type);
+ const isSelected = m === mmsi;
+ markers[m].setIcon(createVesselMarkerIcon(heading, vesselType, isSelected));
}
});
@@ -595,13 +648,13 @@
function showVesselDetails(vessel) {
const container = document.getElementById('selectedInfo');
- const icon = getShipIcon(vessel.ship_type);
+ const iconSvg = getShipIconSvg(vessel.ship_type, 28);
const category = getShipCategory(vessel.ship_type);
const navStatus = NAV_STATUS[vessel.nav_status] || vessel.nav_status_text || 'Unknown';
container.innerHTML = `