diff --git a/application/controllers/Map.php b/application/controllers/Map.php index bcae4da3e..e827f3834 100644 --- a/application/controllers/Map.php +++ b/application/controllers/Map.php @@ -3,10 +3,199 @@ class Map extends CI_Controller { + function __construct() + { + parent::__construct(); + $this->load->helper(array('form', 'url', 'psr4_autoloader')); + + $this->load->model('user_model'); + if (!$this->user_model->authorize(2)) { + $this->session->set_flashdata('error', __("You're not allowed to do that!")); + redirect('dashboard'); + } + } + function index() { redirect('dashboard'); } + /** + * QSO Map with country selection and OpenStreetMap + */ + public function qso_map() { + $this->load->library('Geojson'); + $this->load->model('Map_model'); + + // Get supported DXCC countries with state data + $supported_dxccs = $this->geojson->getSupportedDxccs(); + + // Fetch available countries from the logbook + $countries = $this->Map_model->get_available_countries(); + + // Filter countries to only include those with GeoJSON support + $supported_country_codes = array_keys($supported_dxccs); + $filtered_countries = array_filter($countries, function($country) use ($supported_country_codes) { + return in_array($country['COL_DXCC'], $supported_country_codes); + }); + + // Fetch station profiles + $station_profiles = $this->Map_model->get_station_profiles(); + + $data['countries'] = $filtered_countries; + $data['station_profiles'] = $station_profiles; + $data['supported_dxccs'] = $supported_dxccs; + + $this->load->view('interface_assets/header', $data); + $this->load->view('map/qso_map', $data); + $this->load->view('interface_assets/footer'); + } + + /** + * AJAX endpoint to get QSO data for a specific country + */ + public function get_qsos_for_country() { + $this->load->model('Map_model'); + $this->load->library('Geojson'); + $country = $this->input->post('country'); + $dxcc = $this->input->post('dxcc'); + $station_id = $this->input->post('station_id'); + + if (empty($country)) { + while (ob_get_level()) ob_end_clean(); + $this->output + ->set_content_type('application/json') + ->set_output(json_encode(['error' => 'Country not specified'])); + return; + } + + // Convert "all" to null for all stations + $station_id = ($station_id === 'all') ? null : $station_id; + + try { + $qsos = $this->Map_model->get_qsos_by_country($country, $station_id, $limit); + + if (empty($qsos)) { + while (ob_get_level()) ob_end_clean(); + $this->output + ->set_content_type('application/json') + ->set_output(json_encode(['error' => 'No QSOs found with 6+ character gridsquares'])); + return; + } + } catch (Exception $e) { + while (ob_get_level()) ob_end_clean(); + $this->output + ->set_content_type('application/json') + ->set_output(json_encode(['error' => 'Database query failed: ' . $e->getMessage()])); + return; + } + + // Check if QSOs are inside GeoJSON boundaries + try { + if ($country === 'all') { + // For all countries, optimize by caching GeoJSON files and checking in batches + $geojsonCache = []; + foreach ($qsos as &$qso) { + if ($qso['COL_DXCC'] && $this->geojson->isStateSupported($qso['COL_DXCC'])) { + $dxcc = $qso['COL_DXCC']; + + // Cache GeoJSON data to avoid repeated file loading + if (!isset($geojsonCache[$dxcc])) { + $geojsonFile = "assets/json/geojson/states_{$dxcc}.geojson"; + $geojsonCache[$dxcc] = $this->geojson->loadGeoJsonFile($geojsonFile); + } + + $geojsonData = $geojsonCache[$dxcc]; + if ($geojsonData !== null) { + $state = $this->geojson->findFeatureContainingPoint($qso['lat'], $qso['lng'], $geojsonData); + $qso['inside_geojson'] = ($state !== null); + $qso['state_info'] = $state; + } else { + $qso['inside_geojson'] = true; // Assume inside if no GeoJSON file + $qso['state_info'] = null; + } + } else { + $qso['inside_geojson'] = true; // Assume inside for countries without GeoJSON + $qso['state_info'] = null; + } + } + // Free cache memory + unset($geojsonCache); + } elseif ($dxcc && $this->geojson->isStateSupported($dxcc)) { + // For single country, use original logic + $geojsonFile = "assets/json/geojson/states_{$dxcc}.geojson"; + $geojsonData = $this->geojson->loadGeoJsonFile($geojsonFile); + + if ($geojsonData !== null) { + // Check each QSO if it's inside the GeoJSON + foreach ($qsos as &$qso) { + $state = $this->geojson->findFeatureContainingPoint($qso['lat'], $qso['lng'], $geojsonData); + $qso['inside_geojson'] = ($state !== null); + $qso['state_info'] = $state; + } + } + } + } catch (Exception $e) { + // If GeoJSON processing fails, log error but continue without boundary checking + log_message('error', 'GeoJSON processing error: ' . $e->getMessage()); + foreach ($qsos as &$qso) { + if (!isset($qso['inside_geojson'])) { + $qso['inside_geojson'] = true; + $qso['state_info'] = null; + } + } + } + + // Clear any output buffers that might contain warnings/errors + while (ob_get_level()) { + ob_end_clean(); + } + + // Set proper content type header + $this->output + ->set_content_type('application/json') + ->set_output(json_encode($qsos)); + } + + /** + * Get country boundaries as GeoJSON + */ + public function get_country_geojson($dxcc) { + $this->load->library('Geojson'); + $geojsonFile = "assets/json/geojson/states_{$dxcc}.geojson"; + $geojsonData = $this->geojson->loadGeoJsonFile($geojsonFile); + + if ($geojsonData === null) { + echo json_encode(['error' => 'GeoJSON file not found']); + return; + } + + $this->output + ->set_content_type('application/json') + ->set_output(json_encode($geojsonData)); + } + + /** + * Get all supported DXCC countries with GeoJSON + */ + public function get_all_supported_countries() { + $this->load->library('Geojson'); + $supported_dxccs = $this->geojson->getSupportedDxccs(); + + $country_list = []; + foreach ($supported_dxccs as $dxcc => $data) { + $geojsonFile = "assets/json/geojson/states_{$dxcc}.geojson"; + if (file_exists(FCPATH . $geojsonFile)) { + $country_list[] = [ + 'dxcc' => $dxcc, + 'name' => $data['name'], + 'geojson_file' => $geojsonFile + ]; + } + } + + echo json_encode($country_list); + } + // Generic fonction for return Json for MAP // public function map_plot_json() { $this->load->model('Stations'); @@ -25,17 +214,4 @@ class Map extends CI_Controller { echo json_encode(array_merge($plot_array, $station_array)); } - // Generic fonction for return Json for MAP // - public function glob_plot() { - $footerData = []; - $footerData['scripts'] = [ - 'assets/js/globe/globe.gl.js?' . filemtime(realpath(__DIR__ . "/../../assets/js/globe/globe.gl.js")), - 'assets/js/sections/globe.js?' . filemtime(realpath(__DIR__ . "/../../assets/js/sections/globe.js")), - ]; - $this->load->view('interface_assets/header'); - $this->load->view('globe/index'); - $this->load->view('interface_assets/footer',$footerData); - - } - } diff --git a/application/models/Map_model.php b/application/models/Map_model.php new file mode 100644 index 000000000..b20b29d0e --- /dev/null +++ b/application/models/Map_model.php @@ -0,0 +1,183 @@ +db->select('DISTINCT COL_COUNTRY, COL_DXCC, COUNT(*) as qso_count', FALSE); + $this->db->from($this->config->item('table_name')); + $this->db->join('station_profile', 'station_profile.station_id = ' . $this->config->item('table_name') . '.station_id'); + $this->db->where('station_profile.user_id', $this->session->userdata('user_id')); + $this->db->where('COL_COUNTRY IS NOT NULL'); + $this->db->where('COL_COUNTRY !=', ''); + $this->db->where("LENGTH(COL_GRIDSQUARE) >=", 6); // At least 6 chars + $this->db->group_by('COL_COUNTRY, COL_DXCC'); + $this->db->order_by('COL_COUNTRY'); + + $query = $this->db->get(); + return $query->result_array(); + } + + /** + * Get available station profiles for the user + */ + public function get_station_profiles() { + $this->db->select('station_profile.station_id, station_profile.station_profile_name', FALSE); + $this->db->from($this->config->item('table_name')); + $this->db->join('station_profile', 'station_profile.station_id = ' . $this->config->item('table_name') . '.station_id'); + $this->db->where('station_profile.user_id', $this->session->userdata('user_id')); + $this->db->group_by('station_profile.station_id, station_profile.station_profile_name'); + $this->db->order_by('station_profile.station_profile_name'); + + $query = $this->db->get(); + return $query->result_array(); + } + + /** + * Get QSOs for a specific country with 6+ character grids + */ + public function get_qsos_by_country($country, $station_id = null) { + if (!$this->load->is_loaded('Qra')) { + $this->load->library('Qra'); + } + if (!$this->load->is_loaded('DxccFlag')) { + $this->load->library('DxccFlag'); + } + + $this->db->select('COL_PRIMARY_KEY, COL_CALL, COL_GRIDSQUARE, COL_COUNTRY, COL_DXCC, COL_MODE, COL_BAND, COL_TIME_ON, COL_RST_SENT, COL_RST_RCVD, station_profile.station_profile_name', FALSE); + $this->db->from($this->config->item('table_name')); + $this->db->join('station_profile', 'station_profile.station_id = ' . $this->config->item('table_name') . '.station_id'); + $this->db->where('station_profile.user_id', $this->session->userdata('user_id')); + $this->db->where('COL_COUNTRY', $country); + + // Add station filter if specified + if ($station_id !== null && $station_id !== '') { + $this->db->where('station_profile.station_id', $station_id); + } + $this->db->where("LENGTH(COL_GRIDSQUARE) >=", 6); // At least 6 chars + + $this->db->order_by('COL_TIME_ON', 'DESC'); + + $query = $this->db->get(); + $qsos = $query->result_array(); + + // Process QSOs and convert gridsquares to coordinates + $result = []; + foreach ($qsos as $qso) { + $gridsquare = strtoupper(trim($qso['COL_GRIDSQUARE'])); + + // Only include QSOs with 6+ character grids + if (strlen($gridsquare) >= 6) { + $coords = $this->qra->qra2latlong($gridsquare); + + if ($coords !== false && is_array($coords) && count($coords) >= 2) { + $result[] = [ + 'call' => $qso['COL_CALL'], + 'gridsquare' => $gridsquare, + 'country' => $qso['COL_COUNTRY'], + 'dxcc' => $qso['COL_DXCC'], + 'mode' => $qso['COL_MODE'], + 'band' => $qso['COL_BAND'], + 'time_on' => $qso['COL_TIME_ON'], + 'rst_sent' => $qso['COL_RST_SENT'], + 'rst_rcvd' => $qso['COL_RST_RCVD'], + 'lat' => $coords[0], + 'lng' => $coords[1], + 'profile' => $qso['station_profile_name'], + 'popup' => $this->createContentMessageDx($qso) + ]; + } + } + } + + return $result; + } + + /** + * Generate HTML content for QSO popup display + */ + public function createContentMessageDx($qso) { + $table = ''; + + // Callsign with flag + $table .= ''; + $table .= ''; + $table .= ''; + + // Date/Time + $table .= ''; + $table .= ''; + $datetime = date('Y-m-d H:i', strtotime($qso['COL_TIME_ON'])); + $table .= ''; + $table .= ''; + + // Band/Satellite + $table .= ''; + if (!empty($qso['COL_SAT_NAME'])) { + $table .= ''; + $table .= ''; + } else { + $table .= ''; + $table .= ''; + } + $table .= ''; + + // Mode + $table .= ''; + $table .= ''; + $table .= ''; + $table .= ''; + + // Gridsquare + if (!empty($qso['COL_GRIDSQUARE'])) { + $table .= ''; + $table .= ''; + $table .= ''; + $table .= ''; + } + + // Distance (if available) + if (isset($qso['distance'])) { + $table .= ''; + $table .= ''; + $table .= ''; + $table .= ''; + } + + // Bearing (if available) + if (isset($qso['bearing'])) { + $table .= ''; + $table .= ''; + $table .= ''; + $table .= ''; + } + + // Station Profile + if (!empty($qso['station_profile_name'])) { + $table .= ''; + $table .= ''; + $table .= ''; + $table .= ''; + } + + $table .= '
'; + + if (!empty($qso['COL_DXCC'])) { + $dxccFlag = $this->dxccflag->get($qso['COL_DXCC']); + $table .= '
' . htmlspecialchars($dxccFlag) . '
'; + } + + // Replace zeros with Ø in callsign + $callsign = str_replace('0', 'Ø', $qso['COL_CALL']); + $table .= '' . htmlspecialchars($callsign) . '
'; + $table .= '
Date/Time' . htmlspecialchars($datetime) . '
BandSAT ' . htmlspecialchars($qso['COL_SAT_NAME']); + if (!empty($qso['COL_SAT_MODE'])) { + $table .= ' (' . htmlspecialchars($qso['COL_SAT_MODE']) . ')'; + } + $table .= 'Band' . htmlspecialchars($qso['COL_BAND']) . '
Mode' . htmlspecialchars($qso['COL_MODE']) . '
Gridsquare' . htmlspecialchars($qso['COL_GRIDSQUARE']) . '
Distance' . htmlspecialchars($qso['distance']) . '
Bearing' . htmlspecialchars($qso['bearing']) . '
Station' . htmlspecialchars($qso['station_profile_name']) . '
'; + + return $table; + } +} diff --git a/application/views/interface_assets/header.php b/application/views/interface_assets/header.php index 82ce0be81..f4102b4db 100644 --- a/application/views/interface_assets/header.php +++ b/application/views/interface_assets/header.php @@ -340,6 +340,8 @@ + + diff --git a/application/views/map/qso_map.php b/application/views/map/qso_map.php new file mode 100644 index 000000000..9422e96fe --- /dev/null +++ b/application/views/map/qso_map.php @@ -0,0 +1,461 @@ +
+

+ +
+
+ + +
+
+ + +
+
+ +
+
+
+ + +
+
+
+
+ Loading... +
+
+
+
+ + + + +
+ + + + +