diff --git a/application/controllers/Logbookadvanced.php b/application/controllers/Logbookadvanced.php index d78fb41de..d6c994d82 100644 --- a/application/controllers/Logbookadvanced.php +++ b/application/controllers/Logbookadvanced.php @@ -100,6 +100,7 @@ class Logbookadvanced extends CI_Controller { 'assets/js/leaflet/geocoding.js', 'assets/js/globe/globe.gl.js?' . filemtime(realpath(__DIR__ . "/../../assets/js/globe/globe.gl.js")), 'assets/js/bootstrap-multiselect.js?' . filemtime(realpath(__DIR__ . "/../../assets/js/bootstrap-multiselect.js")), + 'assets/js/leaflet/L.MaidenheadColouredGridMap.js', ]; $this->load->view('interface_assets/header', $data); @@ -927,4 +928,20 @@ class Logbookadvanced extends CI_Controller { print json_encode($result); } + function showMapForIncorrectGrid() { + if(!clubaccess_check(9)) return; + + $this->load->model('logbookadvanced_model'); + $dxcc = $this->input->post('dxcc', true); + + $data['grids'] = $this->logbookadvanced_model->getGridsForDxcc($dxcc); + $data['dxcc'] = $dxcc; + $data['gridsquare'] = $this->input->post('gridsquare', true); + $dxccname = $this->input->post('dxccname', true); + $data['title'] = sprintf(__("Map for DXCC %s and gridsquare %s."), $dxccname, $data['gridsquare']); + + header("Content-Type: application/json"); + print json_encode($data); + } + } diff --git a/application/models/Logbookadvanced_model.php b/application/models/Logbookadvanced_model.php index dc4243479..3b0694e5c 100644 --- a/application/models/Logbookadvanced_model.php +++ b/application/models/Logbookadvanced_model.php @@ -2190,4 +2190,15 @@ class Logbookadvanced_model extends CI_Model { return $allResults; } + + function getGridsForDxcc($dxcc) { + $sql = "select group_concat(distinct gridsquare order by gridsquare separator ', ') grids + from vuccgrids + where adif = ?"; + + $query = $this->db->query($sql, array($dxcc)); + $row = $query->row(); + + return $row->grids; + } } diff --git a/application/views/logbookadvanced/checkresult.php b/application/views/logbookadvanced/checkresult.php index 15d31875d..83ea007e5 100644 --- a/application/views/logbookadvanced/checkresult.php +++ b/application/views/logbookadvanced/checkresult.php @@ -165,6 +165,7 @@ function check_incorrect_gridsquares($result, $custom_date_format) { ?> + @@ -193,6 +194,7 @@ function check_incorrect_gridsquares($result, $custom_date_format) { ?> } ?> + ')"> @@ -207,6 +209,7 @@ function check_incorrect_gridsquares($result, $custom_date_format) { ?> + diff --git a/application/views/logbookadvanced/index.php b/application/views/logbookadvanced/index.php index 589f86426..6e0521ed7 100644 --- a/application/views/logbookadvanced/index.php +++ b/application/views/logbookadvanced/index.php @@ -77,7 +77,8 @@ let lang_gen_advanced_logbook_show_more = ''; let lang_gen_advanced_logbook_show_less = ''; - + let lang_gen_advanced_logbook_confirmedLabel = ''; + let lang_gen_advanced_logbook_workedLabel = ''; let homegrid =''; = 2) { + const lonIdx = d1.indexOf(grid[0]); + const latIdx = d1.indexOf(grid[1]); + if (lonIdx >= 0 && latIdx >= 0) { + lon += lonIdx * 20; + lat += latIdx * 10; + lonWidth = 20; + latHeight = 10; + } + } + + // Second pair (square) + if (grid.length >= 4) { + const lonIdx = parseInt(grid[2]); + const latIdx = parseInt(grid[3]); + if (!isNaN(lonIdx) && !isNaN(latIdx)) { + lon += lonIdx * 2; + lat += latIdx * 1; + lonWidth = 2; + latHeight = 1; + } + } + + // Third pair (subsquare) + if (grid.length >= 6) { + const lonIdx = d2.indexOf(grid[4]); + const latIdx = d2.indexOf(grid[5]); + if (lonIdx >= 0 && latIdx >= 0) { + lon += lonIdx * (2 / 24); + lat += latIdx * (1 / 24); + lonWidth = 2 / 24; + latHeight = 1 / 24; + } + } + + return L.latLngBounds([lat, lon], [lat + latHeight, lon + lonWidth]); + } + + function showMapForIncorrectGrid(gridsquare, dxcc, dxccname) { + $.ajax({ + url: base_url + 'index.php/logbookadvanced/showMapForIncorrectGrid', + type: 'post', + data: { + gridsquare: gridsquare, + dxcc: dxcc, + dxccname: dxccname + }, + success: function (data) { + // Add metadata to data object + data.gridsquareDisplay = gridsquare; + data.dxccnameDisplay = dxccname; + + BootstrapDialog.show({ + title: data.title, + size: BootstrapDialog.SIZE_WIDE, + cssClass: 'mapdialog', + nl2br: false, + message: '
', + onshown: function(dialog) { + drawMap(data); + }, + buttons: [{ + label: lang_admin_close, + action: function (dialogItself) { + dialogItself.close(); + } + }] + }); + } + }); + } + + function drawMap(data) { + if (typeof(user_map_custom.qsoconfirm) !== 'undefined') { + confirmedColor = user_map_custom.qsoconfirm.color; + } + if (typeof(user_map_custom.qso) !== 'undefined') { + workedColor = user_map_custom.qso.color; + } + let container = L.DomUtil.get('mapgridcontainer'); + + if(container != null){ + container._leaflet_id = null; + container.remove(); + $(".mapgridcontent").html('
'); + } + + // Initialize global arrays for colored maidenhead overlay + if (typeof grid_two === 'undefined') grid_two = []; + if (typeof grid_four === 'undefined') grid_four = []; + if (typeof grid_six === 'undefined') grid_six = []; + if (typeof grid_two_confirmed === 'undefined') grid_two_confirmed = []; + if (typeof grid_four_confirmed === 'undefined') grid_four_confirmed = []; + if (typeof grid_six_confirmed === 'undefined') grid_six_confirmed = []; + + // Clear arrays + grid_two.length = 0; + grid_four.length = 0; + grid_six.length = 0; + grid_two_confirmed.length = 0; + grid_four_confirmed.length = 0; + grid_six_confirmed.length = 0; + grids = data.grids; + + // Process data.grids - mark in green (confirmed) + if (data.grids) { + // data.grids can be a comma-separated string or an array + let gridsArray = Array.isArray(data.grids) ? data.grids : data.grids.split(',').map(g => g.trim()); + gridsArray.forEach(function(grid) { + let gridUpper = grid.toUpperCase(); + if (gridUpper.length === 2) { + grid_two_confirmed.push(gridUpper); + grid_two.push(gridUpper); // Also add to worked so it shows up + } else if (gridUpper.length === 4) { + grid_four_confirmed.push(gridUpper); + grid_four.push(gridUpper); // Also add to worked so it shows up + } else if (gridUpper.length === 6) { + grid_six_confirmed.push(gridUpper); + grid_six.push(gridUpper); // Also add to worked so it shows up + } + }); + } + + // Process data.gridsquare - mark first 4 letters in red (worked) + if (data.gridsquare) { + let gridsquareUpper = data.gridsquare.toUpperCase().substring(0, 4); + if (gridsquareUpper.length >= 2) { + let twoChar = gridsquareUpper.substring(0, 2); + if (!grid_two_confirmed.includes(twoChar)) { + grid_two.push(twoChar); + } + } + if (gridsquareUpper.length >= 4) { + let fourChar = gridsquareUpper.substring(0, 4); + if (!grid_four_confirmed.includes(fourChar)) { + grid_four.push(fourChar); + } + } + } + + // Collect all grids to calculate bounds for auto-zoom + // Include both data.grids (green) and data.gridsquare (red) + let allGrids = []; + if (data.grids) { + let gridsArray = Array.isArray(data.grids) ? data.grids : data.grids.split(',').map(g => g.trim()); + allGrids = allGrids.concat(gridsArray); + } + if (data.gridsquare) { + allGrids.push(data.gridsquare.substring(0, Math.min(4, data.gridsquare.length))); + } + + // Calculate bounds and center for auto-zoom + let bounds = null; + let centerLat = 0; + let centerLng = 0; + let minLat = 90; + let maxLat = -90; + let allLngs = []; + + allGrids.forEach(function(grid) { + let gridBounds = maidenheadToBounds(grid); + if (gridBounds) { + // Track center points and extents for better handling + let gridCenter = gridBounds.getCenter(); + centerLat += gridCenter.lat; + allLngs.push(gridCenter.lng); + + if (gridBounds.getSouth() < minLat) minLat = gridBounds.getSouth(); + if (gridBounds.getNorth() > maxLat) maxLat = gridBounds.getNorth(); + + if (bounds) { + bounds.extend(gridBounds); + } else { + bounds = gridBounds; + } + } + }); + + // Calculate average center + if (allLngs.length > 0) { + centerLat = centerLat / allGrids.length; + + // Check if longitudes span more than 180° (crossing antimeridian or covering large area) + let minLng = Math.min(...allLngs); + let maxLng = Math.max(...allLngs); + let lngSpan = maxLng - minLng; + + if (lngSpan > 300) { + // Spans nearly the entire globe (like Asiatic Russia from -180 to 180) + // Use a predefined sensible center for such cases + centerLng = 120; // Center of Asiatic Russia/mainland Russia + } else if (lngSpan > 180) { + // When spanning >180°, we should go the "other way around" the globe + // Add 360° to any negative longitudes, then average, then normalize back + let wrappedLngs = allLngs.map(lng => lng < 0 ? lng + 360 : lng); + let avgWrapped = wrappedLngs.reduce((a, b) => a + b, 0) / wrappedLngs.length; + + // Normalize to -180 to 180 range + if (avgWrapped > 180) avgWrapped -= 360; + centerLng = avgWrapped; + } else { + // Normal case - simple average + centerLng = allLngs.reduce((a, b) => a + b, 0) / allLngs.length; + } + } + + // Make map global for L.MaidenheadColouredGridMap.js + window.map = new L.Map('mapgridcontainer', { + fullscreenControl: true, + fullscreenControlOptions: { + position: 'topleft' + }, + }); + + let maidenhead = L.maidenhead().addTo(window.map); + + let osmUrl = option_map_tile_server; + let osmAttrib= option_map_tile_server_copyright; + let osm = new L.TileLayer(osmUrl, {minZoom: 1, maxZoom: 12, attribution: osmAttrib}); + + let redIcon = L.icon({ + iconUrl: icon_dot_url, + iconSize: [10, 10], // size of the icon + }); + + window.map.addLayer(osm); + + // Add legend + let legend = L.control({position: 'topright'}); + legend.onAdd = function (map) { + let div = L.DomUtil.create('div', 'info legend'); + div.style.backgroundColor = 'white'; + div.style.padding = '10px'; + div.style.borderRadius = '5px'; + div.style.boxShadow = '0 0 10px rgba(0,0,0,0.2)'; + + div.innerHTML = + '
' + + '
' + + '' + lang_gen_advanced_logbook_confirmedLabel + ' ' + data.dxccnameDisplay + '' + + '
' + + '
' + + '
' + + '' + lang_gen_advanced_logbook_workedLabel + ' ' + data.gridsquareDisplay + '' + + '
'; + return div; + }; + legend.addTo(window.map); + + // Zoom to fit all grids with padding + if (bounds) { + const latSpan = maxLat - minLat; + const lngSpan = Math.max(...allLngs) - Math.min(...allLngs); + + // For extremely large spans (near 360° like Asiatic Russia), use manual center + // For moderate spans (100-200° like Japan+GM05), use fitBounds with lower maxZoom + // For smaller spans, use fitBounds normally + + if (lngSpan > 300) { + // Spans nearly the entire globe - use calculated center with fixed zoom + let zoom = 3; // Increased from 2 to 3 for better detail + window.map.setView([centerLat, centerLng], zoom); + } else if (lngSpan > 100) { + // Large span (like Japan to western hemisphere) - use fitBounds but limit zoom + window.map.fitBounds(bounds, { padding: [30, 30], maxZoom: 3 }); + } else { + // Normal case - use fitBounds + let maxZoom = 10; + if (lngSpan < 50) maxZoom = 7; + if (lngSpan < 20) maxZoom = 10; + window.map.fitBounds(bounds, { padding: [50, 50], maxZoom: maxZoom }); + } + } else { + window.map.setView([30, 0], 1.5); + } + }