// MarineTraffic-style ship classification // Maps S&P STAT5CODE prefixes, VesselType strings, and custom typecodes to MT categories // AIS Ship Type Number → category (ITU-R M.1371-5 Table 50) // 20: Wing in ground, 30: Fishing, 31-32: Towing, 33: Dredging, 34: Diving ops // 35: Military, 36: Sailing, 37: Pleasure, 40-49: High speed, 50: Pilot // 60-69: Passenger, 70-79: Cargo, 80-89: Tanker, 90-99: Other function classifyAisShipType(code: string): string | null { const num = parseInt(code, 10); if (isNaN(num)) return null; if (num === 30) return 'fishing'; if (num >= 31 && num <= 34) return 'tug_special'; if (num === 35) return 'military'; if (num === 36) return 'pleasure'; if (num === 37) return 'pleasure'; if (num >= 40 && num <= 49) return 'high_speed'; if (num === 50 || num === 51 || num === 52 || num === 53 || num === 54 || num === 55) return 'tug_special'; if (num >= 60 && num <= 69) return 'passenger'; if (num >= 70 && num <= 79) return 'cargo'; if (num >= 80 && num <= 89) return 'tanker'; return null; } export function getMarineTrafficCategory(typecode?: string, category?: string): string { if (!typecode) { // Fallback to our internal category if (category === 'tanker') return 'tanker'; if (category === 'cargo') return 'cargo'; if (category === 'fishing') return 'fishing'; if (category === 'destroyer' || category === 'warship' || category === 'carrier' || category === 'patrol') return 'military'; if (category === 'civilian') return 'unspecified'; return 'unspecified'; } const code = typecode.toUpperCase(); // AIS Ship Type number (e.g. "30" = fishing) const aisResult = classifyAisShipType(code); if (aisResult) return aisResult; // Our custom typecodes (exact match) if (code === 'VLCC' || code === 'LNG' || code === 'LPG') return 'tanker'; if (code === 'CONT' || code === 'BULK') return 'cargo'; if (code === 'DDH' || code === 'DDG' || code === 'CVN' || code === 'FFG' || code === 'LCS' || code === 'MCM' || code === 'PC' || code === 'LPH') return 'military'; if (code === 'PASS') return 'passenger'; if (code === 'FISH' || code === 'TRAWL') return 'fishing'; if (code === 'FISHINGGEAR') return 'fishing_gear'; // VesselType strings (e.g. "Cargo", "Tanker", "Passenger") — match BEFORE STAT5CODE // to avoid "Cargo" matching STAT5CODE prefix "C" → fishing const lower = code.toLowerCase(); if (lower.includes('tanker')) return 'tanker'; if (lower.includes('cargo') || lower.includes('container') || lower.includes('bulk')) return 'cargo'; if (lower.includes('passenger') || lower.includes('cruise') || lower.includes('ferry')) return 'passenger'; if (lower.includes('fishing') || lower.includes('trawl') || lower.includes('trawler')) return 'fishing'; if (lower.includes('tug') || lower.includes('supply') || lower.includes('offshore')) return 'tug_special'; if (lower.includes('high speed')) return 'high_speed'; if (lower.includes('pleasure') || lower.includes('yacht') || lower.includes('sailing')) return 'pleasure'; if (lower.includes('pilot') || lower.includes('search') || lower.includes('law enforcement')) return 'tug_special'; if (lower.includes('naval') || lower.includes('military')) return 'military'; // S&P STAT5CODE (IHS StatCode5) — 2nd char is digit (e.g. "A1xxxx", "B2xxxx") if (code.length >= 2 && /\d/.test(code[1])) { if (code.startsWith('A1')) return 'tanker'; if (code.startsWith('A2')) return 'cargo'; if (code.startsWith('A3')) return 'cargo'; if (code.startsWith('B')) return 'passenger'; if (code.startsWith('C')) return 'fishing'; if (code.startsWith('D')) return 'tug_special'; if (code.startsWith('E')) return 'tug_special'; if (code.startsWith('X')) return 'unspecified'; } return 'unspecified'; }