import React from 'react'; import './APRSSymbol.scss'; export interface APRSSymbolProps { /** * APRS symbol as 2-character string: table + code * Examples: "/!" (police station), "\!" (emergency), "/-" (house) * * First character is table identifier: * - '/' for primary table * - '\' for secondary table * - alphanumeric (0-9, A-Z) for overlay * * Second character is the symbol code */ symbol: string; /** * Symbol size in pixels (default: 24) * Available sizes: 24, 32, 48, 56, 64, 128, 256 */ size?: 24 | 32 | 48 | 56 | 64 | 128 | 256; /** * Alternative text for accessibility */ alt?: string; /** * Additional CSS class name */ className?: string; } /** * Get the table ID for the sprite filename * - Primary table (/): 0 * - Secondary table (\): 1 * - Overlay characters: 2 */ const getTableId = (table: string): number => { if (table === '/') return 0; if (table === '\\') return 1; return 2; // Overlay }; /** * Calculate sprite position based on ASCII character code * Symbols are arranged in a 16-column grid, ordered by ASCII value */ const getSymbolPosition = (table: string, code: string): { row: number; col: number } | null => { if (!code || code.length !== 1) return null; const charCode = code.charCodeAt(0); // Primary and secondary tables use characters from ! (33) to } (125) if (table === '/' || table === '\\') { if (charCode < 33 || charCode > 125) return null; const index = charCode - 33; return { row: Math.floor(index / 16), col: index % 16 }; } // Overlay characters: 0-9 (48-57), A-Z (65-90) if (charCode >= 48 && charCode <= 57) { // 0-9 const index = charCode - 48; return { row: Math.floor(index / 16), col: index % 16 }; } else if (charCode >= 65 && charCode <= 90) { // A-Z const index = 10 + (charCode - 65); return { row: Math.floor(index / 16), col: index % 16 }; } return null; }; /** * React component for rendering APRS symbols from sprite sheets */ export const APRSSymbol: React.FC = ({ symbol, size = 24, alt, className = '' }) => { // Parse the symbol string (format: table + code) if (!symbol || symbol.length < 2) { // Return empty div if symbol is invalid return
; } const table = symbol.charAt(0); const code = symbol.charAt(1); const tableId = getTableId(table); const position = getSymbolPosition(table, code); if (!position) { // Return empty div if symbol is invalid return
; } const { row, col } = position; // Build image paths for different resolutions const imagePath1x = `/image/protocol/aprs/aprs-symbols-${size}-${tableId}.png`; const imagePath2x = `/image/protocol/aprs/aprs-symbols-${size}-${tableId}@2x.png`; const imagePath3x = `/image/protocol/aprs/aprs-symbols-${size}-${tableId}@3x.png`; // Calculate background position const bgX = -(col * size); const bgY = -(row * size); // Use image-set for retina display support const backgroundImage = [ `-webkit-image-set(url('${imagePath1x}') 1x, url('${imagePath2x}') 2x, url('${imagePath3x}') 3x)`, `image-set(url('${imagePath1x}') 1x, url('${imagePath2x}') 2x, url('${imagePath3x}') 3x)`, `url('${imagePath1x}')` // Fallback ].join(', '); const style: React.CSSProperties = { backgroundImage, backgroundPosition: `${bgX}px ${bgY}px`, width: `${size}px`, height: `${size}px` }; return (
); }; export default APRSSymbol;