diff --git a/server.js b/server.js
index abd7562..5326969 100644
--- a/server.js
+++ b/server.js
@@ -1590,21 +1590,57 @@ function extractGridFromComment(comment) {
function estimateLocationFromPrefix(callsign) {
if (!callsign) return null;
+ // Handle slash/portable callsigns: TI5/N3KS, F/G3ABC, W1ABC/7, N3KS/P
+ let lookupCall = callsign.toUpperCase().trim();
+ if (lookupCall.includes('/')) {
+ const parts = lookupCall.split('/');
+ // Common suffixes to ignore (operating modifiers, not location)
+ const modifierSuffixes = ['P', 'M', 'MM', 'AM', 'QRP', 'R', 'T', 'B', 'LH'];
+
+ if (parts.length === 2) {
+ const [left, right] = parts;
+
+ // Right side is a modifier suffix like /P, /M, /MM, /QRP
+ if (modifierSuffixes.includes(right)) {
+ lookupCall = left;
+ }
+ // Right side is a single digit (district change): W1ABC/7
+ else if (/^\d$/.test(right)) {
+ // Replace the district digit in the callsign
+ lookupCall = left.replace(/\d/, right);
+ }
+ // Left side looks like a short prefix (1-4 chars, e.g. TI5/, F/, VP8/)
+ // and right side looks like a full callsign
+ else if (left.length <= 4 && right.length > left.length) {
+ lookupCall = left; // Use the operating prefix (TI5, F, VP8, etc.)
+ }
+ // Right side looks like a short prefix and left is the full callsign
+ else if (right.length <= 4 && left.length > right.length) {
+ lookupCall = right;
+ }
+ // Default: use the longer part (more likely the actual callsign)
+ else {
+ lookupCall = left.length >= right.length ? left : right;
+ }
+ }
+ }
+
+ const upper = lookupCall.toUpperCase();
+
// Comprehensive prefix to grid mapping
- // Uses typical/central grid for each prefix area
+ // Uses typical/central grid for each DXCC entity
const prefixGrids = {
- // USA - by call district
- 'W1': 'FN41', 'K1': 'FN41', 'N1': 'FN41', 'AA1': 'FN41', // New England
- 'W2': 'FN20', 'K2': 'FN20', 'N2': 'FN20', 'AA2': 'FN20', // NY/NJ
- 'W3': 'FM19', 'K3': 'FM19', 'N3': 'FM19', 'AA3': 'FM19', // PA/MD/DE
- 'W4': 'EM73', 'K4': 'EM73', 'N4': 'EM73', 'AA4': 'EM73', // SE USA
- 'W5': 'EM12', 'K5': 'EM12', 'N5': 'EM12', 'AA5': 'EM12', // TX/OK/LA/AR/MS
- 'W6': 'CM97', 'K6': 'CM97', 'N6': 'CM97', 'AA6': 'CM97', // California
- 'W7': 'DN31', 'K7': 'DN31', 'N7': 'DN31', 'AA7': 'DN31', // Pacific NW/Mountain
- 'W8': 'EN81', 'K8': 'EN81', 'N8': 'EN81', 'AA8': 'EN81', // MI/OH/WV
- 'W9': 'EN52', 'K9': 'EN52', 'N9': 'EN52', 'AA9': 'EN52', // IL/IN/WI
- 'W0': 'EN31', 'K0': 'EN31', 'N0': 'EN31', 'AA0': 'EN31', // Central USA
- // Generic USA (no district) - AA through AL are all US prefixes
+ // === USA - by call district ===
+ 'W1': 'FN41', 'K1': 'FN41', 'N1': 'FN41', 'AA1': 'FN41',
+ 'W2': 'FN20', 'K2': 'FN20', 'N2': 'FN20', 'AA2': 'FN20',
+ 'W3': 'FM19', 'K3': 'FM19', 'N3': 'FM19', 'AA3': 'FM19',
+ 'W4': 'EM73', 'K4': 'EM73', 'N4': 'EM73', 'AA4': 'EM73',
+ 'W5': 'EM12', 'K5': 'EM12', 'N5': 'EM12', 'AA5': 'EM12',
+ 'W6': 'CM97', 'K6': 'CM97', 'N6': 'CM97', 'AA6': 'CM97',
+ 'W7': 'DN31', 'K7': 'DN31', 'N7': 'DN31', 'AA7': 'DN31',
+ 'W8': 'EN81', 'K8': 'EN81', 'N8': 'EN81', 'AA8': 'EN81',
+ 'W9': 'EN52', 'K9': 'EN52', 'N9': 'EN52', 'AA9': 'EN52',
+ 'W0': 'EN31', 'K0': 'EN31', 'N0': 'EN31', 'AA0': 'EN31',
'W': 'EM79', 'K': 'EM79', 'N': 'EM79',
'AA': 'EM79', 'AB': 'EM79', 'AC': 'EM79', 'AD': 'EM79', 'AE': 'EM79', 'AF': 'EM79',
'AG': 'EM79', 'AH': 'EM79', 'AI': 'EM79', 'AJ': 'EM79', 'AK': 'EM79', 'AL': 'EM79',
@@ -1626,155 +1662,331 @@ function estimateLocationFromPrefix(callsign) {
'AK0': 'EN31', 'AK1': 'FN41', 'AK2': 'FN20', 'AK3': 'FM19', 'AK4': 'EM73',
'AK5': 'EM12', 'AK6': 'CM97', 'AK7': 'DN31', 'AK8': 'EN81', 'AK9': 'EN52',
'AL0': 'EN31', 'AL1': 'FN41', 'AL2': 'FN20', 'AL3': 'FM19', 'AL4': 'EM73',
- 'AL5': 'EM12', 'AL6': 'CM97', 'AL7': 'BP51', 'AL8': 'EN81', 'AL9': 'EN52', // AL7 = Alaska
-
- // Canada - by province
- 'VE1': 'FN74', 'VA1': 'FN74', // Maritime
- 'VE2': 'FN35', 'VA2': 'FN35', // Quebec
- 'VE3': 'FN03', 'VA3': 'FN03', // Ontario
- 'VE4': 'EN19', 'VA4': 'EN19', // Manitoba
- 'VE5': 'DO51', 'VA5': 'DO51', // Saskatchewan
- 'VE6': 'DO33', 'VA6': 'DO33', // Alberta
- 'VE7': 'CN89', 'VA7': 'CN89', // British Columbia
- 'VE8': 'DP31', 'VA8': 'DP31', // NWT
- 'VE9': 'FN65', 'VA9': 'FN65', // New Brunswick
- 'VY1': 'CP28', // Yukon
- 'VY2': 'FN86', // PEI
- 'VO1': 'GN37', 'VO2': 'GO17', // Newfoundland/Labrador
- 'VE': 'FN03', 'VA': 'FN03', // Generic Canada
-
- // UK & Ireland
- 'G': 'IO91', 'M': 'IO91', '2E': 'IO91', 'GW': 'IO81', // England/Wales
+ 'AL5': 'EM12', 'AL6': 'CM97', 'AL7': 'BP51', 'AL8': 'EN81', 'AL9': 'EN52',
+
+ // === US Territories ===
+ 'KH0': 'QK25', 'KH1': 'BL01', 'KH2': 'QK24', 'KH3': 'BJ11', 'KH4': 'BL01',
+ 'KH5': 'BL01', 'KH6': 'BL01', 'KH7': 'BL01', 'KH8': 'AH37', 'KH9': 'BK29',
+ 'KP1': 'FK68', 'KP2': 'FK77', 'KP3': 'FK68', 'KP4': 'FK68', 'KP5': 'FK85',
+ 'KL7': 'BP51', 'NL7': 'BP51', 'WL7': 'BP51', 'KL': 'BP51', // Alaska
+ 'NP4': 'FK68', 'WP4': 'FK68', 'NP3': 'FK68', 'WP3': 'FK68', // Puerto Rico
+
+ // === Canada - by province ===
+ 'VE1': 'FN74', 'VA1': 'FN74',
+ 'VE2': 'FN35', 'VA2': 'FN35',
+ 'VE3': 'FN03', 'VA3': 'FN03',
+ 'VE4': 'EN19', 'VA4': 'EN19',
+ 'VE5': 'DO51', 'VA5': 'DO51',
+ 'VE6': 'DO33', 'VA6': 'DO33',
+ 'VE7': 'CN89', 'VA7': 'CN89',
+ 'VE8': 'DP31', 'VA8': 'DP31',
+ 'VE9': 'FN65', 'VA9': 'FN65',
+ 'VY1': 'CP28', 'VY2': 'FN86',
+ 'VO1': 'GN37', 'VO2': 'GO17',
+ 'VE': 'FN03', 'VA': 'FN03',
+ 'CY0': 'FN93', // Sable Island
+ 'CY9': 'GN06', // St. Paul Island
+
+ // === UK & Ireland ===
+ 'G': 'IO91', 'M': 'IO91', '2E': 'IO91', // England
+ 'GW': 'IO81', 'MW': 'IO81', '2W': 'IO81', // Wales
'GM': 'IO85', 'MM': 'IO85', '2M': 'IO85', // Scotland
'GI': 'IO64', 'MI': 'IO64', '2I': 'IO64', // N. Ireland
+ 'GD': 'IO74', 'MD': 'IO74', '2D': 'IO74', // Isle of Man
+ 'GJ': 'IN89', 'MJ': 'IN89', '2J': 'IN89', // Jersey
+ 'GU': 'IN89', 'MU': 'IN89', '2U': 'IN89', // Guernsey
'EI': 'IO63', 'EJ': 'IO63', // Ireland
-
- // Germany
- 'DL': 'JO51', 'DJ': 'JO51', 'DK': 'JO51', 'DA': 'JO51', 'DB': 'JO51', 'DC': 'JO51', 'DD': 'JO51', 'DF': 'JO51', 'DG': 'JO51', 'DH': 'JO51', 'DO': 'JO51',
-
- // Rest of Europe
- 'F': 'JN18', // France
- 'I': 'JN61', 'IK': 'JN45', 'IZ': 'JN61', // Italy
- 'EA': 'IN80', 'EC': 'IN80', 'EB': 'IN80', // Spain
- 'CT': 'IM58', // Portugal
- 'PA': 'JO21', 'PD': 'JO21', 'PE': 'JO21', 'PH': 'JO21', // Netherlands
- 'ON': 'JO20', 'OO': 'JO20', 'OR': 'JO20', 'OT': 'JO20', // Belgium
+ 'GS': 'IO85', 'MS': 'IO85', // Scotland special
+ 'GB': 'IO91', // UK special event
+ 'GX': 'IO91', // UK special
+
+ // === Germany ===
+ 'DL': 'JO51', 'DJ': 'JO51', 'DK': 'JO51', 'DA': 'JO51', 'DB': 'JO51',
+ 'DC': 'JO51', 'DD': 'JO51', 'DF': 'JO51', 'DG': 'JO51', 'DH': 'JO51',
+ 'DI': 'JO51', 'DM': 'JO51', 'DN': 'JO51', 'DO': 'JO51', 'DP': 'JO51', 'DR': 'JO51',
+
+ // === Rest of Europe ===
+ 'F': 'JN18', 'TM': 'JN18', // France
+ 'I': 'JN61', 'IK': 'JN45', 'IZ': 'JN61', 'IU': 'JN61', 'IW': 'JN61', 'IX': 'JN61', // Italy
+ 'IS': 'JM49', 'IM': 'JM49', // Sardinia
+ 'IT9': 'JM68', // Sicily
+ 'EA': 'IN80', 'EC': 'IN80', 'EB': 'IN80', 'ED': 'IN80', 'EE': 'IN80', 'EF': 'IN80', 'EG': 'IN80', 'EH': 'IN80', // Spain
+ 'EA6': 'JM19', // Balearic Islands
+ 'EA8': 'IL18', // Canary Islands
+ 'EA9': 'IM75', // Ceuta & Melilla
+ 'CT': 'IM58', 'CS': 'IM58', 'CQ': 'IM58', // Portugal
+ 'CT3': 'IM12', // Madeira
+ 'CU': 'HM58', // Azores
+ 'PA': 'JO21', 'PD': 'JO21', 'PE': 'JO21', 'PH': 'JO21', 'PI': 'JO21', // Netherlands
+ 'ON': 'JO20', 'OO': 'JO20', 'OR': 'JO20', 'OT': 'JO20', 'OP': 'JO20', // Belgium
+ 'LX': 'JN39', // Luxembourg
'HB': 'JN47', 'HB9': 'JN47', // Switzerland
+ 'HB0': 'JN47', // Liechtenstein
+ 'HE': 'JN47', // Switzerland special
'OE': 'JN78', // Austria
- 'OZ': 'JO55', 'OU': 'JO55', // Denmark
- 'SM': 'JO89', 'SA': 'JO89', 'SB': 'JO89', 'SE': 'JO89', // Sweden
- 'LA': 'JO59', 'LB': 'JO59', // Norway
- 'OH': 'KP20', 'OF': 'KP20', 'OG': 'KP20', 'OI': 'KP20', // Finland
- 'SP': 'JO91', 'SQ': 'JO91', 'SO': 'JO91', '3Z': 'JO91', // Poland
+ 'OZ': 'JO55', 'OU': 'JO55', 'OV': 'JO55', 'OW': 'JO55', 'OX': 'JO55', '5P': 'JO55', '5Q': 'JO55', // Denmark
+ 'OX': 'GP46', // Greenland (override generic Denmark)
+ 'OY': 'IP62', // Faroe Islands
+ 'SM': 'JO89', 'SA': 'JO89', 'SB': 'JO89', 'SC': 'JO89', 'SD': 'JO89', 'SE': 'JO89', 'SF': 'JO89', 'SG': 'JO89', 'SH': 'JO89', 'SI': 'JO89', 'SJ': 'JO89', 'SK': 'JO89', 'SL': 'JO89', // Sweden
+ 'LA': 'JO59', 'LB': 'JO59', 'LC': 'JO59', 'LD': 'JO59', 'LE': 'JO59', 'LF': 'JO59', 'LG': 'JO59', 'LH': 'JO59', 'LI': 'JO59', 'LJ': 'JO59', 'LK': 'JO59', 'LL': 'JO59', 'LM': 'JO59', 'LN': 'JO59', // Norway
+ 'JW': 'JQ78', // Svalbard
+ 'JX': 'IQ50', // Jan Mayen
+ 'OH': 'KP20', 'OF': 'KP20', 'OG': 'KP20', 'OH0': 'JP90', 'OI': 'KP20', // Finland, Aland
+ 'SP': 'JO91', 'SQ': 'JO91', 'SO': 'JO91', 'SN': 'JO91', '3Z': 'JO91', 'HF': 'JO91', // Poland
'OK': 'JN79', 'OL': 'JN79', // Czech Republic
'OM': 'JN88', // Slovakia
'HA': 'JN97', 'HG': 'JN97', // Hungary
- 'YO': 'KN34', // Romania
+ 'YO': 'KN34', 'YP': 'KN34', 'YQ': 'KN34', 'YR': 'KN34', // Romania
'LZ': 'KN22', // Bulgaria
- 'YU': 'KN04', // Serbia
+ 'YU': 'KN04', 'YT': 'KN04', // Serbia
+ 'Z3': 'KN11', // North Macedonia
'9A': 'JN75', // Croatia
'S5': 'JN76', // Slovenia
- 'SV': 'KM17', 'SX': 'KM17', // Greece
+ 'T9': 'JN83', // Bosnia-Herzegovina
+ '4O': 'JN92', // Montenegro
+ 'ZA': 'JN91', // Albania
+ 'SV': 'KM17', 'SX': 'KM17', 'SY': 'KM17', 'SZ': 'KM17', 'SW': 'KM17', // Greece
+ 'SV5': 'KM46', 'SV9': 'KM25', // Dodecanese, Crete
+ 'J4': 'KM17', // Greece special
'9H': 'JM75', // Malta
'LY': 'KO24', // Lithuania
'ES': 'KO29', // Estonia
'YL': 'KO26', // Latvia
-
- // Russia & Ukraine
- 'UA': 'KO85', 'RA': 'KO85', 'RU': 'KO85', 'RV': 'KO85', 'RW': 'KO85', 'RX': 'KO85', 'RZ': 'KO85',
+ 'TF': 'HP94', // Iceland
+ 'TK': 'JN42', // Corsica
+ 'TA': 'KN30', 'TC': 'KN30', 'TB': 'KN30', 'YM': 'KN30', // Turkey
+ 'ER': 'KN46', // Moldova
+ '1A': 'JN61', // Sovereign Military Order of Malta
+ '4U': 'JN36', // United Nations (Geneva)
+ 'T7': 'JN63', // San Marino
+ '3A': 'JN33', // Monaco
+ 'C3': 'JN12', // Andorra
+
+ // === Russia & CIS ===
+ 'UA': 'KO85', 'RA': 'KO85', 'RU': 'KO85', 'RV': 'KO85', 'RW': 'KO85', 'RX': 'KO85', 'RZ': 'KO85', 'R1': 'KO85', 'R2': 'KO85', 'R3': 'KO85', 'R4': 'KO85', 'R5': 'KO85', 'R6': 'KO85',
'UA0': 'OO33', 'RA0': 'OO33', 'R0': 'OO33', // Asiatic Russia
'UA9': 'MO06', 'RA9': 'MO06', 'R9': 'MO06', // Ural
- 'UR': 'KO50', 'UT': 'KO50', 'UX': 'KO50', 'US': 'KO50', // Ukraine
-
- // Japan - by call area
+ 'UR': 'KO50', 'UT': 'KO50', 'UX': 'KO50', 'US': 'KO50', 'UY': 'KO50', 'UW': 'KO50', // Ukraine
+ 'EU': 'KO33', 'EV': 'KO33', 'EW': 'KO33', // Belarus
+ 'UN': 'MN83', 'UP': 'MN83', 'UQ': 'MN83', // Kazakhstan
+ 'EX': 'MN51', // Kyrgyzstan
+ 'EY': 'MM38', // Tajikistan
+ 'UK': 'MN41', // Uzbekistan
+ 'EZ': 'LN99', // Turkmenistan
+ '4J': 'LN40', '4K': 'LN40', // Azerbaijan
+ '4L': 'LN11', // Georgia
+ 'EK': 'LN20', // Armenia
+
+ // === Japan ===
'JA1': 'PM95', 'JH1': 'PM95', 'JR1': 'PM95', 'JE1': 'PM95', 'JF1': 'PM95', 'JG1': 'PM95', 'JI1': 'PM95', 'JJ1': 'PM95', 'JK1': 'PM95', 'JL1': 'PM95', 'JM1': 'PM95', 'JN1': 'PM95', 'JO1': 'PM95', 'JP1': 'PM95', 'JQ1': 'PM95', 'JS1': 'PM95', '7K1': 'PM95', '7L1': 'PM95', '7M1': 'PM95', '7N1': 'PM95',
'JA2': 'PM84', 'JA3': 'PM74', 'JA4': 'PM64', 'JA5': 'PM63', 'JA6': 'PM53', 'JA7': 'QM07', 'JA8': 'QN02', 'JA9': 'PM86', 'JA0': 'PM97',
- 'JA': 'PM95', 'JH': 'PM95', 'JR': 'PM95', 'JE': 'PM95', 'JF': 'PM95', 'JG': 'PM95', // Generic Japan
+ 'JA': 'PM95', 'JH': 'PM95', 'JR': 'PM95', 'JE': 'PM95', 'JF': 'PM95', 'JG': 'PM95', 'JI': 'PM95', 'JJ': 'PM95', 'JK': 'PM95', 'JL': 'PM95', 'JM': 'PM95', 'JN': 'PM95', 'JO': 'PM95', 'JP': 'PM95', 'JQ': 'PM95', 'JS': 'PM95',
+ '7J': 'PM95', '7K': 'PM95', '7L': 'PM95', '7M': 'PM95', '7N': 'PM95',
+ '8J': 'PM95', '8N': 'PM95', // Japan special event
- // Rest of Asia
+ // === Rest of Asia ===
'HL': 'PM37', 'DS': 'PM37', '6K': 'PM37', '6L': 'PM37', // South Korea
- 'BV': 'PL04', 'BW': 'PL04', 'BX': 'PL04', // Taiwan
- 'BY': 'OM92', 'BT': 'OM92', 'BA': 'OM92', 'BD': 'OM92', 'BG': 'OM92', // China
- 'VU': 'MK82', 'VU2': 'MK82', 'VU3': 'MK82', // India
+ 'BV': 'PL04', 'BW': 'PL04', 'BX': 'PL04', 'BM': 'PL04', 'BN': 'PL04', 'BO': 'PL04', 'BP': 'PL04', 'BQ': 'PL04', // Taiwan
+ 'BY': 'OM92', 'BT': 'OM92', 'BA': 'OM92', 'BD': 'OM92', 'BG': 'OM92', 'BH': 'OM92', 'BI': 'OM92', 'BJ': 'OM92', 'BL': 'OM92', 'BR': 'OM92', 'BS': 'OM92', // China
+ 'VR': 'OL72', // Hong Kong
+ 'XX9': 'OL62', // Macau
+ 'VU': 'MK82', 'VU2': 'MK82', 'VU3': 'MK82', 'AT': 'MK82', // India
+ 'AP': 'MM42', // Pakistan
+ '4S': 'NJ06', // Sri Lanka
+ 'XW': 'NK96', // Laos
+ 'XU': 'OK34', // Cambodia
+ 'XV': 'OK30', '3W': 'OK30', // Vietnam
'HS': 'OK03', 'E2': 'OK03', // Thailand
'9V': 'OJ11', // Singapore
'9M': 'OJ05', '9W': 'OJ05', // Malaysia
+ 'V8': 'OJ85', // Brunei
'DU': 'PK04', 'DV': 'PK04', 'DW': 'PK04', 'DX': 'PK04', 'DY': 'PK04', 'DZ': 'PK04', '4D': 'PK04', '4E': 'PK04', '4F': 'PK04', '4G': 'PK04', '4H': 'PK04', '4I': 'PK04', // Philippines
'YB': 'OI33', 'YC': 'OI33', 'YD': 'OI33', 'YE': 'OI33', 'YF': 'OI33', 'YG': 'OI33', 'YH': 'OI33', // Indonesia
+ 'JT': 'ON09', 'JU': 'ON09', 'JV': 'ON09', // Mongolia
- // Oceania
- 'VK': 'QF56', 'VK1': 'QF44', 'VK2': 'QF56', 'VK3': 'QF22', 'VK4': 'QG62', 'VK5': 'PF95', 'VK6': 'OF86', 'VK7': 'QE38', // Australia
+ // === Middle East ===
+ 'A4': 'LL93', 'A41': 'LL93', 'A45': 'LL93', // Oman
+ 'A6': 'LL65', 'A61': 'LL65', // UAE
+ 'A7': 'LL45', 'A71': 'LL45', // Qatar
+ 'A9': 'LL46', 'A92': 'LL46', // Bahrain
+ '9K': 'LL47', // Kuwait
+ 'HZ': 'LL24', '7Z': 'LL24', '8Z': 'LL24', // Saudi Arabia
+ '4X': 'KM72', '4Z': 'KM72', // Israel
+ 'OD': 'KM73', // Lebanon
+ 'JY': 'KM72', // Jordan
+ 'YK': 'KM74', // Syria
+ 'YI': 'LM32', // Iraq
+ 'EP': 'LL38', // Iran
+
+ // === Oceania ===
+ 'VK': 'QF56', 'VK1': 'QF44', 'VK2': 'QF56', 'VK3': 'QF22', 'VK4': 'QG62', 'VK5': 'PF95', 'VK6': 'OF86', 'VK7': 'QE38', 'VK8': 'PG36', 'VK9': 'QF56', // Australia
+ 'AX': 'QF56', // Australia special
'ZL': 'RF70', 'ZL1': 'RF72', 'ZL2': 'RF70', 'ZL3': 'RE66', 'ZL4': 'RE54', // New Zealand
- 'KH6': 'BL01', // Hawaii
- 'KH2': 'QK24', // Guam
+ 'ZM': 'RF70', // New Zealand special
'FK': 'RG37', // New Caledonia
-
- // South America
- 'LU': 'GF05', 'LW': 'GF05', 'LO': 'GF05', 'L2': 'GF05', 'L3': 'GF05', 'L4': 'GF05', 'L5': 'GF05', 'L6': 'GF05', 'L7': 'GF05', 'L8': 'GF05', 'L9': 'GF05', // Argentina
+ 'FO': 'BH51', // French Polynesia
+ 'FW': 'AH33', // Wallis & Futuna
+ '3D2': 'RH91', // Fiji
+ '5W': 'AH44', // Samoa
+ 'KH0': 'QK25', // Mariana Islands
+ 'KH8': 'AH37', // American Samoa
+ 'T8': 'PJ77', // Palau
+ 'V7': 'RJ38', // Marshall Islands
+ 'T3': 'RI92', // Kiribati
+ 'A3': 'AH22', // Tonga
+ 'ZK3': 'AI51', // Tokelau
+ 'E5': 'BG08', // Cook Islands
+ 'VK9N': 'RH23', // Norfolk Island
+ 'VK0M': 'QE37', // Macquarie Island
+
+ // === South America ===
+ 'LU': 'GF05', 'LW': 'GF05', 'LO': 'GF05', 'LQ': 'GF05', 'LR': 'GF05', 'LS': 'GF05', 'LT': 'GF05', 'LV': 'GF05',
+ 'L2': 'GF05', 'L3': 'GF05', 'L4': 'GF05', 'L5': 'GF05', 'L6': 'GF05', 'L7': 'GF05', 'L8': 'GF05', 'L9': 'GF05', // Argentina
+ 'AY': 'GF05', 'AZ': 'GF05', // Argentina special
'PY': 'GG87', 'PP': 'GG87', 'PQ': 'GG87', 'PR': 'GG87', 'PS': 'GG87', 'PT': 'GG87', 'PU': 'GG87', 'PV': 'GG87', 'PW': 'GG87', 'PX': 'GG87', // Brazil
+ 'ZV': 'GG87', 'ZW': 'GG87', 'ZX': 'GG87', 'ZY': 'GG87', 'ZZ': 'GG87', // Brazil special
'CE': 'FF46', 'CA': 'FF46', 'CB': 'FF46', 'CC': 'FF46', 'CD': 'FF46', 'XQ': 'FF46', 'XR': 'FF46', '3G': 'FF46', // Chile
- 'CX': 'GF15', // Uruguay
+ 'CE0': 'DG52', // Easter Island
+ 'CX': 'GF15', 'CV': 'GF15', 'CW': 'GF15', // Uruguay
'HC': 'FI09', 'HD': 'FI09', // Ecuador
+ 'HC8': 'EI49', // Galapagos
'OA': 'FH17', 'OB': 'FH17', 'OC': 'FH17', // Peru
'HK': 'FJ35', 'HJ': 'FJ35', '5J': 'FJ35', '5K': 'FJ35', // Colombia
+ 'HK0': 'EJ96', // San Andres & Providencia
'YV': 'FK60', 'YW': 'FK60', 'YX': 'FK60', 'YY': 'FK60', // Venezuela
-
- // Caribbean
+ 'CP': 'FH63', // Bolivia
+ 'ZP': 'FG99', // Paraguay
+ '8R': 'GJ25', // Guyana
+ 'PZ': 'GJ15', // Suriname
+ 'FY': 'GJ35', // French Guiana
+
+ // === Central America ===
+ 'TI': 'EJ79', // Costa Rica
+ 'HP': 'FJ08', // Panama
+ 'TG': 'EK44', // Guatemala
+ 'YS': 'EK53', // El Salvador
+ 'HR': 'EK65', // Honduras
+ 'YN': 'EK72', // Nicaragua
+ 'V3': 'EK57', // Belize
+ 'XE': 'EK09', 'XA': 'EK09', 'XB': 'EK09', 'XC': 'EK09', 'XD': 'EK09', '4A': 'EK09', '4B': 'EK09', '4C': 'EK09', '6D': 'EK09', '6E': 'EK09', '6F': 'EK09', '6G': 'EK09', '6H': 'EK09', '6I': 'EK09', // Mexico
+
+ // === Caribbean ===
'KP4': 'FK68', 'NP4': 'FK68', 'WP4': 'FK68', // Puerto Rico
'VP5': 'FL31', // Turks & Caicos
'HI': 'FK49', // Dominican Republic
- 'CO': 'FL10', 'CM': 'FL10', // Cuba
+ 'CO': 'FL10', 'CM': 'FL10', 'T4': 'FL10', // Cuba
'FG': 'FK96', // Guadeloupe
'FM': 'FK94', // Martinique
- 'PJ': 'FK52', // Netherlands Antilles
-
- // Africa
+ 'PJ2': 'FK52', 'PJ4': 'FK52', // Curacao, Bonaire
+ 'PJ5': 'FK87', 'PJ6': 'FK87', 'PJ7': 'FK88', // Saba, St Eustatius, St Maarten
+ 'HH': 'FK38', // Haiti
+ '6Y': 'FK18', // Jamaica
+ 'VP2M': 'FK86', // Montserrat
+ 'VP2V': 'FK78', // British Virgin Islands
+ 'VP2E': 'FK97', // Anguilla
+ 'VP9': 'FM72', // Bermuda
+ 'V2': 'FK97', // Antigua & Barbuda
+ 'V4': 'FK87', // Saint Kitts & Nevis
+ 'J3': 'FK92', // Grenada
+ 'J6': 'FK93', // St. Lucia
+ 'J7': 'FK95', // Dominica
+ 'J8': 'FK93', // St. Vincent
+ '8P': 'GK03', // Barbados
+ '9Y': 'FK90', '9Z': 'FK90', // Trinidad & Tobago
+ 'C6': 'FL16', // Bahamas
+ 'ZF': 'EK99', // Cayman Islands
+ 'FJ': 'FK87', // St. Barthelemy
+ 'FS': 'FK88', // St. Martin
+
+ // === Africa ===
'ZS': 'KG33', 'ZR': 'KG33', 'ZT': 'KG33', 'ZU': 'KG33', // South Africa
- '5N': 'JJ55', // Nigeria
+ '5N': 'JJ55', '5O': 'JJ55', // Nigeria
'CN': 'IM63', // Morocco
'7X': 'JM16', // Algeria
+ 'TS': 'JM44', '3V': 'JM44', // Tunisia
+ '5A': 'JM73', // Libya
'SU': 'KL30', // Egypt
'5Z': 'KI88', // Kenya
'ET': 'KJ49', // Ethiopia
+ '5H': 'KI73', // Tanzania
+ '9J': 'KH44', // Zambia
+ '9X': 'KI49', // Rwanda
+ '9U': 'KI39', // Burundi
+ '5X': 'KI42', // Uganda
+ 'EL': 'IJ56', // Liberia
+ '6W': 'IK14', // Senegal
+ 'C5': 'IK13', // Gambia
+ '5T': 'IL33', // Mauritania
+ '9G': 'IJ95', // Ghana
+ 'TU': 'IJ46', // Ivory Coast
+ 'TY': 'JJ17', // Benin
+ '5V': 'JJ17', // Togo
+ 'XT': 'IK52', // Burkina Faso
+ 'TZ': 'IK52', // Mali
+ 'TJ': 'JJ43', // Cameroon
+ 'TN': 'JI74', // Congo (Brazzaville)
+ '9Q': 'JI55', // DR Congo
+ 'TR': 'JJ10', // Gabon
+ 'TL': 'JJ64', // Central African Republic
+ 'ST': 'KK25', // Sudan
+ 'J5': 'IK21', // Guinea-Bissau
+ '3X': 'IJ56', // Guinea
+ '9L': 'IJ38', // Sierra Leone
+ 'A2': 'KG42', // Botswana
+ 'V5': 'JG87', // Namibia
+ '7P': 'KG30', // Lesotho
+ '3DA': 'KG53', // Eswatini
+ 'Z2': 'KG51', 'Z21': 'KG51', // Zimbabwe
+ '7Q': 'KH74', // Malawi
+ 'C9': 'KH53', // Mozambique
+ '8Q': 'MJ56', // Maldives
+ '3B8': 'LG89', // Mauritius
+ 'FR': 'LG78', // Reunion
+ 'S7': 'LI75', // Seychelles
+ 'D6': 'LI24', // Comoros
+ 'FT': 'ME27', // Crozet / Kerguelen / Amsterdam
+ '3B9': 'MH43', // Rodrigues
+ '5R': 'LH67', // Madagascar
+ 'V4': 'FK87', // St Kitts (Caribbean, already above)
'EA8': 'IL18', 'EA9': 'IM75', // Canary Islands, Ceuta
-
- // Middle East
- 'A4': 'LL93', 'A41': 'LL93', 'A45': 'LL93', // Oman
- 'A6': 'LL65', 'A61': 'LL65', // UAE
- 'A7': 'LL45', 'A71': 'LL45', // Qatar
- 'HZ': 'LL24', // Saudi Arabia
- '4X': 'KM72', '4Z': 'KM72', // Israel
- 'OD': 'KM73', // Lebanon
-
- // Other
+ 'D4': 'HK76', // Cape Verde
+ '6V': 'IK14', '6W': 'IK14', // Senegal
+ 'J2': 'LK10', // Djibouti
+ 'E3': 'KK15', // Eritrea
+ 'T5': 'KI62', '6O': 'KI62', // Somalia
+ 'S0': 'IL33', // Western Sahara
+
+ // === South Atlantic ===
'VP8': 'GD18', // Falkland Islands
- 'CE9': 'FC56', 'DP0': 'IB59', 'KC4': 'FC56', // Antarctica
- 'SV5': 'KM46', 'SV9': 'KM25', // Dodecanese, Crete
+ 'VP8/G': 'IC86', 'VP8/H': 'IC86', // South Georgia, South Sandwich
+ 'ZD7': 'II74', // St. Helena
+ 'ZD8': 'II22', // Ascension Island
+ 'ZD9': 'IG35', // Tristan da Cunha
+ '3Y': 'JD04', // Bouvet Island
+
+ // === Antarctica ===
+ 'CE9': 'FC56', 'DP0': 'IB59', 'KC4': 'FC56', 'RI1AN': 'MC14',
+ 'VP8/': 'GC04', // Various Antarctic bases
+ '8J1RL': 'QC52', // Japan Antarctic
+
+ // === Other ===
+ 'ZB': 'IM76', 'ZG': 'IM76', // Gibraltar
+ 'ZC4': 'KM64', // UK Sovereign Bases Cyprus
+ '5B': 'KM64', 'C4': 'KM64', 'H2': 'KM64', 'P3': 'KM64', // Cyprus
};
- const upper = callsign.toUpperCase();
-
// Smart US callsign detection - US prefixes follow specific patterns
// K, N, W + anything = USA
// A[A-L] + digit = USA (e.g., AA0, AE5, AL7)
const usCallPattern = /^([KNW][0-9]?|A[A-L][0-9])/;
const usMatch = upper.match(usCallPattern);
if (usMatch) {
- // Extract call district (the digit) for more precise location
const districtMatch = upper.match(/^[KNWA][A-L]?([0-9])/);
const district = districtMatch ? districtMatch[1] : null;
const usDistrictGrids = {
- '0': 'EN31', // Central (CO, IA, KS, MN, MO, NE, ND, SD)
- '1': 'FN41', // New England (CT, MA, ME, NH, RI, VT)
- '2': 'FN20', // NY, NJ
- '3': 'FM19', // PA, MD, DE
- '4': 'EM73', // Southeast (AL, FL, GA, KY, NC, SC, TN, VA)
- '5': 'EM12', // TX, OK, LA, AR, MS, NM
- '6': 'CM97', // California
- '7': 'DN31', // Pacific NW/Mountain (AZ, ID, MT, NV, OR, UT, WA, WY)
- '8': 'EN81', // MI, OH, WV
- '9': 'EN52', // IL, IN, WI
+ '0': 'EN31', '1': 'FN41', '2': 'FN20', '3': 'FM19', '4': 'EM73',
+ '5': 'EM12', '6': 'CM97', '7': 'DN31', '8': 'EN81', '9': 'EN52',
};
const grid = district && usDistrictGrids[district] ? usDistrictGrids[district] : 'EM79';
@@ -1782,60 +1994,31 @@ function estimateLocationFromPrefix(callsign) {
if (gridLoc) {
return {
callsign,
- lat: gridLoc.lat,
- lon: gridLoc.lon,
- grid: grid,
- country: 'USA',
- estimated: true,
- source: 'prefix-grid'
+ lat: gridLoc.lat, lon: gridLoc.lon, grid,
+ country: 'USA', estimated: true, source: 'prefix-grid'
};
}
}
- // Try longest prefix match first (up to 4 chars) for non-US calls
- for (let len = 4; len >= 1; len--) {
+ // Try longest prefix match first (up to 5 chars) for non-US calls
+ for (let len = Math.min(upper.length, 5); len >= 1; len--) {
const prefix = upper.substring(0, len);
if (prefixGrids[prefix]) {
const gridLoc = maidenheadToLatLon(prefixGrids[prefix]);
if (gridLoc) {
return {
callsign,
- lat: gridLoc.lat,
- lon: gridLoc.lon,
+ lat: gridLoc.lat, lon: gridLoc.lon,
grid: prefixGrids[prefix],
country: getCountryFromPrefix(prefix),
- estimated: true,
- source: 'prefix-grid'
+ estimated: true, source: 'prefix-grid'
};
}
}
}
- // Fallback to first character (most likely country for each letter)
- const firstCharGrids = {
- 'A': 'EM79', 'B': 'PL02', 'C': 'FN03', 'D': 'JO51', 'E': 'IO63', // A=USA (AA-AL), B=China, C=Canada, D=Germany, E=Spain/Ireland
- 'F': 'JN18', 'G': 'IO91', 'H': 'KM72', 'I': 'JN61', 'J': 'PM95', // F=France, G=UK, H=varies, I=Italy, J=Japan
- 'K': 'EM79', 'L': 'GF05', 'M': 'IO91', 'N': 'EM79', 'O': 'KP20', // K=USA, L=Argentina, M=UK, N=USA, O=Finland
- 'P': 'GG87', 'R': 'KO85', 'S': 'JO89', 'T': 'KI88', 'U': 'KO85', // P=Brazil, R=Russia, S=Sweden, T=varies, U=Russia
- 'V': 'QF56', 'W': 'EM79', 'X': 'EK09', 'Y': 'JO91', 'Z': 'KG33' // V=Australia, W=USA, X=Mexico, Y=varies, Z=South Africa
- };
-
- const firstChar = upper[0];
- if (firstCharGrids[firstChar]) {
- const gridLoc = maidenheadToLatLon(firstCharGrids[firstChar]);
- if (gridLoc) {
- return {
- callsign,
- lat: gridLoc.lat,
- lon: gridLoc.lon,
- grid: firstCharGrids[firstChar],
- country: 'Unknown',
- estimated: true,
- source: 'prefix-grid'
- };
- }
- }
-
+ // No match found — return null rather than guess wrong
+ // It's better to not plot a spot than to plot it on the wrong continent
return null;
}
@@ -1845,15 +2028,34 @@ function getCountryFromPrefix(prefix) {
'W': 'USA', 'K': 'USA', 'N': 'USA', 'AA': 'USA',
'VE': 'Canada', 'VA': 'Canada', 'VY': 'Canada', 'VO': 'Canada',
'G': 'England', 'M': 'England', '2E': 'England', 'GM': 'Scotland', 'GW': 'Wales', 'GI': 'N. Ireland',
- 'EI': 'Ireland', 'F': 'France', 'DL': 'Germany', 'I': 'Italy', 'EA': 'Spain', 'CT': 'Portugal',
- 'PA': 'Netherlands', 'ON': 'Belgium', 'HB': 'Switzerland', 'OE': 'Austria',
- 'OZ': 'Denmark', 'SM': 'Sweden', 'LA': 'Norway', 'OH': 'Finland',
- 'SP': 'Poland', 'OK': 'Czech Rep', 'HA': 'Hungary', 'YO': 'Romania', 'LZ': 'Bulgaria',
- 'UA': 'Russia', 'UR': 'Ukraine',
+ 'GD': 'Isle of Man', 'GJ': 'Jersey', 'GU': 'Guernsey', 'GB': 'UK',
+ 'EI': 'Ireland', 'F': 'France', 'TM': 'France', 'TK': 'Corsica',
+ 'DL': 'Germany', 'DJ': 'Germany', 'DK': 'Germany', 'DA': 'Germany', 'DB': 'Germany', 'DC': 'Germany', 'DD': 'Germany', 'DF': 'Germany', 'DG': 'Germany', 'DH': 'Germany', 'DO': 'Germany',
+ 'I': 'Italy', 'IK': 'Italy', 'IZ': 'Italy',
+ 'EA': 'Spain', 'EA6': 'Balearic Is.', 'EA8': 'Canary Is.', 'EA9': 'Ceuta',
+ 'CT': 'Portugal', 'CT3': 'Madeira', 'CU': 'Azores',
+ 'PA': 'Netherlands', 'ON': 'Belgium', 'LX': 'Luxembourg',
+ 'HB': 'Switzerland', 'HB0': 'Liechtenstein', 'OE': 'Austria',
+ 'OZ': 'Denmark', 'OX': 'Greenland', 'OY': 'Faroe Islands',
+ 'SM': 'Sweden', 'LA': 'Norway', 'JW': 'Svalbard', 'OH': 'Finland',
+ 'SP': 'Poland', 'OK': 'Czech Rep.', 'OM': 'Slovakia',
+ 'HA': 'Hungary', 'YO': 'Romania', 'LZ': 'Bulgaria',
+ 'UA': 'Russia', 'RA': 'Russia', 'R': 'Russia',
+ 'UR': 'Ukraine', 'UT': 'Ukraine', 'US': 'Ukraine',
+ 'EU': 'Belarus', 'UN': 'Kazakhstan', 'EK': 'Armenia', '4L': 'Georgia', '4J': 'Azerbaijan',
+ 'YU': 'Serbia', '9A': 'Croatia', 'S5': 'Slovenia', 'T9': 'Bosnia', '4O': 'Montenegro', 'ZA': 'Albania', 'Z3': 'N. Macedonia',
+ 'SV': 'Greece', '9H': 'Malta', 'LY': 'Lithuania', 'ES': 'Estonia', 'YL': 'Latvia',
+ 'TF': 'Iceland', 'TA': 'Turkey', 'ER': 'Moldova', 'T7': 'San Marino', '3A': 'Monaco', 'C3': 'Andorra',
'JA': 'Japan', 'HL': 'S. Korea', 'BV': 'Taiwan', 'BY': 'China', 'VU': 'India', 'HS': 'Thailand',
- 'VK': 'Australia', 'ZL': 'New Zealand', 'KH6': 'Hawaii',
- 'LU': 'Argentina', 'PY': 'Brazil', 'CE': 'Chile', 'HK': 'Colombia', 'YV': 'Venezuela',
- 'ZS': 'South Africa', 'CN': 'Morocco', 'SU': 'Egypt'
+ 'DU': 'Philippines', 'YB': 'Indonesia', '9V': 'Singapore', '9M': 'Malaysia', 'VR': 'Hong Kong',
+ 'VK': 'Australia', 'ZL': 'New Zealand', 'KH6': 'Hawaii', 'KL': 'Alaska',
+ 'LU': 'Argentina', 'PY': 'Brazil', 'ZV': 'Brazil', 'CE': 'Chile', 'CX': 'Uruguay',
+ 'HC': 'Ecuador', 'OA': 'Peru', 'HK': 'Colombia', 'YV': 'Venezuela', 'CP': 'Bolivia', 'ZP': 'Paraguay',
+ 'TI': 'Costa Rica', 'HP': 'Panama', 'TG': 'Guatemala', 'YS': 'El Salvador', 'HR': 'Honduras', 'YN': 'Nicaragua', 'V3': 'Belize',
+ 'XE': 'Mexico', 'HI': 'Dominican Rep.', 'CO': 'Cuba', '6Y': 'Jamaica', 'C6': 'Bahamas', 'ZF': 'Cayman Is.',
+ 'ZS': 'South Africa', 'CN': 'Morocco', 'SU': 'Egypt', '5Z': 'Kenya', 'ET': 'Ethiopia', '5N': 'Nigeria', '5H': 'Tanzania',
+ 'HZ': 'Saudi Arabia', 'A6': 'UAE', 'A7': 'Qatar', '4X': 'Israel', 'OD': 'Lebanon',
+ 'VP8': 'Falkland Is.', 'VP9': 'Bermuda', 'ZB': 'Gibraltar', '5B': 'Cyprus',
};
for (let len = 3; len >= 1; len--) {
diff --git a/src/components/WorldMap.jsx b/src/components/WorldMap.jsx
index 11e8843..4f93e95 100644
--- a/src/components/WorldMap.jsx
+++ b/src/components/WorldMap.jsx
@@ -417,20 +417,33 @@ export const WorldMap = ({
if (showPOTA && potaSpots) {
potaSpots.forEach(spot => {
if (spot.lat && spot.lon) {
- const icon = L.divIcon({
+ // Green triangle marker for POTA activators
+ const triangleIcon = L.divIcon({
className: '',
- html: `${spot.call}`,
- iconSize: null,
- iconAnchor: [0, 0]
+ html: ``,
+ iconSize: [14, 14],
+ iconAnchor: [7, 14]
});
- const marker = L.marker([spot.lat, spot.lon], { icon })
- .bindPopup(`${spot.call}
${spot.ref}
${spot.freq} ${spot.mode}`)
+ const marker = L.marker([spot.lat, spot.lon], { icon: triangleIcon })
+ .bindPopup(`${spot.call}
${spot.ref}
${spot.freq} ${spot.mode}`)
.addTo(map);
potaMarkersRef.current.push(marker);
+
+ // Only show callsign label when labels are enabled
+ if (showDXLabels) {
+ const labelIcon = L.divIcon({
+ className: '',
+ html: `${spot.call}`,
+ iconSize: null,
+ iconAnchor: [0, -2]
+ });
+ const label = L.marker([spot.lat, spot.lon], { icon: labelIcon, interactive: false }).addTo(map);
+ potaMarkersRef.current.push(label);
+ }
}
});
}
- }, [potaSpots, showPOTA]);
+ }, [potaSpots, showPOTA, showDXLabels]);
// Update satellite markers with orbit tracks
useEffect(() => {
@@ -864,7 +877,7 @@ export const WorldMap = ({
{showPOTA && (