From 210f866374b05bf77b88b65a906c3948cbdf953a Mon Sep 17 00:00:00 2001 From: Graham McIntire Date: Mon, 16 Jun 2025 13:56:21 -0500 Subject: [PATCH] map debugging --- assets/js/app.js | 140 ++++++++++ assets/js/minimal_map.js | 220 ++++++++++++++-- lib/aprs_web/controllers/page_controller.ex | 78 ++++++ lib/aprs_web/live/map_live/callsign_view.ex | 51 +++- lib/aprs_web/live/map_live/debug.ex | 273 ++++++++++++++++++++ lib/aprs_web/router.ex | 2 + 6 files changed, 741 insertions(+), 23 deletions(-) create mode 100644 lib/aprs_web/live/map_live/debug.ex diff --git a/assets/js/app.js b/assets/js/app.js index 3948c69..4c041ec 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -27,9 +27,149 @@ let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute(" // Import minimal APRS map hook import MinimalAPRSMap from "./minimal_map.js"; +// Debug Map Hook +let DebugMap = { + mounted() { + console.log("DebugMap hook mounted"); + window.liveSocket.pushEvent("debug_info", { hook_mounted: true }); + + // Check if Leaflet is available + if (typeof L === "undefined") { + console.error("Leaflet library not loaded!"); + window.liveSocket.pushEvent("debug_info", { + leaflet_loaded: false, + errors: ["Leaflet not loaded"], + }); + return; + } + + window.liveSocket.pushEvent("debug_info", { leaflet_loaded: true }); + + // Get initial center and zoom from server-provided data attributes + let initialCenter, initialZoom; + try { + initialCenter = JSON.parse(this.el.dataset.center); + initialZoom = parseInt(this.el.dataset.zoom); + console.log("Parsed data - center:", initialCenter, "zoom:", initialZoom); + } catch (error) { + console.error("Error parsing map data attributes:", error); + window.liveSocket.pushEvent("debug_info", { + errors: ["Error parsing map data: " + error.message], + }); + // Fallback values + initialCenter = { lat: 39.8283, lng: -98.5795 }; + initialZoom = 5; + } + + // Check element dimensions + const rect = this.el.getBoundingClientRect(); + console.log("Map element dimensions:", rect); + + // Initialize basic map + try { + this.map = L.map(this.el, { + zoomControl: true, + attributionControl: true, + closePopupOnClick: true, + }).setView([initialCenter.lat, initialCenter.lng], initialZoom); + + console.log("Map initialized successfully"); + window.liveSocket.pushEvent("debug_info", { map_initialized: true }); + window.debugMapInstance = this.map; + } catch (error) { + console.error("Error initializing map:", error); + window.liveSocket.pushEvent("debug_info", { + map_initialized: false, + errors: ["Map init error: " + error.message], + }); + return; + } + + // Add OpenStreetMap tile layer + try { + const tileLayer = L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", { + attribution: + '© OpenStreetMap contributors | APRS.me', + maxZoom: 19, + }); + tileLayer.addTo(this.map); + console.log("Tile layer added successfully"); + + // Listen for tile layer events + tileLayer.on("loading", () => console.log("Tiles loading...")); + tileLayer.on("load", () => console.log("Tiles loaded")); + tileLayer.on("tileerror", (e) => console.error("Tile error:", e)); + } catch (error) { + console.error("Error adding tile layer:", error); + window.liveSocket.pushEvent("debug_info", { + errors: ["Tile layer error: " + error.message], + }); + } + + // Force initial size calculation + try { + this.map.invalidateSize(); + console.log("Map size invalidated"); + } catch (error) { + console.error("Error invalidating map size:", error); + } + + // Track when map is ready + this.map.whenReady(() => { + console.log("Debug map is ready"); + console.log("Map container size:", this.map.getSize()); + console.log("Map zoom:", this.map.getZoom()); + console.log("Map center:", this.map.getCenter()); + + try { + this.pushEvent("map_ready", {}); + } catch (error) { + console.error("Error in map ready callback:", error); + } + }); + + // Handle resize + window.addEventListener("resize", () => { + console.log("Window resized, invalidating map size"); + try { + this.map.invalidateSize(); + } catch (error) { + console.error("Error invalidating map size on resize:", error); + } + }); + + // Add a delayed size check + setTimeout(() => { + console.log("Delayed map size check"); + const rect = this.el.getBoundingClientRect(); + console.log("Map element dimensions after delay:", rect); + if (this.map) { + try { + this.map.invalidateSize(); + console.log("Map size re-invalidated after delay"); + } catch (error) { + console.error("Error re-invalidating map size:", error); + } + } + }, 1000); + }, + + destroyed() { + console.log("DebugMap hook destroyed"); + if (this.map) { + this.map.remove(); + this.map = null; + } + if (window.debugMapInstance) { + window.debugMapInstance = null; + } + }, +}; + // APRS Map Hook let Hooks = {}; Hooks.APRSMap = MinimalAPRSMap; +Hooks.DebugMap = DebugMap; let liveSocket = new LiveSocket("/live", Socket, { longPollFallbackMs: 2500, diff --git a/assets/js/minimal_map.js b/assets/js/minimal_map.js index f7d858b..68e69c9 100644 --- a/assets/js/minimal_map.js +++ b/assets/js/minimal_map.js @@ -4,39 +4,165 @@ let MinimalAPRSMap = { mounted() { console.log("MinimalAPRSMap hook mounted"); + console.log("Element:", this.el); + console.log("Element dataset:", this.el.dataset); + + // Initialize error tracking + this.errors = []; + this.initializationAttempts = 0; + this.maxInitializationAttempts = 3; + + this.attemptInitialization(); + }, + + attemptInitialization() { + this.initializationAttempts++; + console.log( + `Initialization attempt ${this.initializationAttempts}/${this.maxInitializationAttempts}`, + ); + + // Check if Leaflet is available + if (typeof L === "undefined") { + console.error("Leaflet library not loaded!"); + this.errors.push("Leaflet library not available"); + + if (this.initializationAttempts < this.maxInitializationAttempts) { + console.log("Retrying initialization in 1 second..."); + setTimeout(() => this.attemptInitialization(), 1000); + return; + } else { + this.handleFatalError("Leaflet library failed to load after multiple attempts"); + return; + } + } // Get initial center and zoom from server-provided data attributes - const initialCenter = JSON.parse(this.el.dataset.center); - const initialZoom = parseInt(this.el.dataset.zoom); + let initialCenter, initialZoom; + try { + const centerData = this.el.dataset.center; + const zoomData = this.el.dataset.zoom; + if (!centerData || !zoomData) { + throw new Error("Missing map data attributes"); + } + + initialCenter = JSON.parse(centerData); + initialZoom = parseInt(zoomData); + + // Validate parsed data + if ( + !initialCenter || + typeof initialCenter.lat !== "number" || + typeof initialCenter.lng !== "number" + ) { + throw new Error("Invalid center data"); + } + + if (isNaN(initialZoom) || initialZoom < 1 || initialZoom > 20) { + throw new Error("Invalid zoom data"); + } + + console.log("Parsed data - center:", initialCenter, "zoom:", initialZoom); + } catch (error) { + console.error("Error parsing map data attributes:", error); + console.log("Raw center data:", this.el.dataset.center); + console.log("Raw zoom data:", this.el.dataset.zoom); + + // Fallback values + initialCenter = { lat: 39.8283, lng: -98.5795 }; + initialZoom = 5; + console.log("Using fallback values:", initialCenter, initialZoom); + } + + this.initializeMap(initialCenter, initialZoom); + }, + + initializeMap(initialCenter, initialZoom) { console.log("Initializing minimal map with center:", initialCenter, "and zoom:", initialZoom); + // Check element dimensions + const rect = this.el.getBoundingClientRect(); + console.log("Map element dimensions:", rect); + + // Validate element has dimensions + if (rect.width === 0 || rect.height === 0) { + console.warn("Map element has no dimensions, retrying..."); + this.errors.push("Map element has zero dimensions"); + + if (this.initializationAttempts < this.maxInitializationAttempts) { + setTimeout(() => this.attemptInitialization(), 500); + return; + } else { + this.handleFatalError("Map element never gained proper dimensions"); + return; + } + } + // Initialize basic map - this.map = L.map(this.el, { - zoomControl: true, - attributionControl: true, - closePopupOnClick: true, - }).setView([initialCenter.lat, initialCenter.lng], initialZoom); + try { + this.map = L.map(this.el, { + zoomControl: true, + attributionControl: true, + closePopupOnClick: true, + }).setView([initialCenter.lat, initialCenter.lng], initialZoom); + console.log("Map initialized successfully"); + } catch (error) { + console.error("Error initializing map:", error); + this.errors.push("Map initialization failed: " + error.message); + + if (this.initializationAttempts < this.maxInitializationAttempts) { + setTimeout(() => this.attemptInitialization(), 1000); + return; + } else { + this.handleFatalError("Map initialization failed after multiple attempts"); + return; + } + } // Add OpenStreetMap tile layer - L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", { - attribution: - '© OpenStreetMap contributors | APRS.me', - maxZoom: 19, - }).addTo(this.map); + try { + const tileLayer = L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", { + attribution: + '© OpenStreetMap contributors | APRS.me', + maxZoom: 19, + }); + tileLayer.addTo(this.map); + console.log("Tile layer added successfully"); + + // Listen for tile layer events + tileLayer.on("loading", () => console.log("Tiles loading...")); + tileLayer.on("load", () => console.log("Tiles loaded")); + tileLayer.on("tileerror", (e) => console.error("Tile error:", e)); + } catch (error) { + console.error("Error adding tile layer:", error); + this.errors.push("Tile layer failed: " + error.message); + } // Store markers for management this.markers = new Map(); this.markerLayer = L.layerGroup().addTo(this.map); // Force initial size calculation - this.map.invalidateSize(); + try { + this.map.invalidateSize(); + console.log("Map size invalidated"); + } catch (error) { + console.error("Error invalidating map size:", error); + } // Track when map is ready this.map.whenReady(() => { console.log("Minimal map is ready"); - this.pushEvent("map_ready", {}); - this.sendBoundsToServer(); + console.log("Map container size:", this.map.getSize()); + console.log("Map zoom:", this.map.getZoom()); + console.log("Map center:", this.map.getCenter()); + + try { + this.pushEvent("map_ready", {}); + this.sendBoundsToServer(); + } catch (error) { + console.error("Error in map ready callback:", error); + } }); // Send bounds to LiveView when map moves @@ -48,14 +174,67 @@ let MinimalAPRSMap = { }); // Handle resize - window.addEventListener("resize", () => { - this.map.invalidateSize(); - }); + this.resizeHandler = () => { + console.log("Window resized, invalidating map size"); + try { + if (this.map) { + this.map.invalidateSize(); + } + } catch (error) { + console.error("Error invalidating map size on resize:", error); + } + }; + window.addEventListener("resize", this.resizeHandler); + + // Add a delayed size check + setTimeout(() => { + console.log("Delayed map size check"); + const rect = this.el.getBoundingClientRect(); + console.log("Map element dimensions after delay:", rect); + if (this.map) { + try { + this.map.invalidateSize(); + console.log("Map size re-invalidated after delay"); + } catch (error) { + console.error("Error re-invalidating map size:", error); + } + } + }, 1000); // LiveView event handlers this.setupLiveViewHandlers(); }, + handleFatalError(message) { + console.error("Fatal map error:", message); + console.error("All errors:", this.errors); + + // Display error message to user + if (this.el) { + this.el.innerHTML = ` +
+
+

Map Loading Error

+

${message}

+

+ Please refresh the page or check the browser console for details. +

+
+
+ `; + } + }, + setupLiveViewHandlers() { // Add single marker this.handleEvent("add_marker", (data) => { @@ -346,6 +525,11 @@ let MinimalAPRSMap = { clearTimeout(this.boundsTimer); } + // Clean up event listeners + if (this.resizeHandler) { + window.removeEventListener("resize", this.resizeHandler); + } + // Clean up markers if (this.markerLayer) { this.markerLayer.clearLayers(); diff --git a/lib/aprs_web/controllers/page_controller.ex b/lib/aprs_web/controllers/page_controller.ex index 15f76ca..968899c 100644 --- a/lib/aprs_web/controllers/page_controller.ex +++ b/lib/aprs_web/controllers/page_controller.ex @@ -86,6 +86,84 @@ defmodule AprsWeb.PageController do }) end + def test_map(conn, _params) do + # Simple HTML test page for basic map functionality + html(conn, """ + + + + + + Map Test - APRS.me + + + + +
+ Map Test
+ Status: Initializing...
+
+ ← Back to Main Map +
+
+ + + + + + """) + end + defp format_uptime(seconds) when seconds <= 0, do: "Not connected" defp format_uptime(seconds) do diff --git a/lib/aprs_web/live/map_live/callsign_view.ex b/lib/aprs_web/live/map_live/callsign_view.ex index e73f12b..7867bd6 100644 --- a/lib/aprs_web/live/map_live/callsign_view.ex +++ b/lib/aprs_web/live/map_live/callsign_view.ex @@ -13,6 +13,7 @@ defmodule AprsWeb.MapLive.CallsignView do def mount(%{"callsign" => callsign}, _session, socket) do # Normalize callsign to uppercase normalized_callsign = String.upcase(callsign) + IO.puts("CallsignView: Mounting for callsign #{normalized_callsign}") # Calculate one hour ago for packet age filtering one_hour_ago = DateTime.add(DateTime.utc_now(), -3600, :second) @@ -33,6 +34,8 @@ defmodule AprsWeb.MapLive.CallsignView do }, map_center: @default_center, map_zoom: @default_zoom, + default_center: @default_center, + default_zoom: @default_zoom, # Replay controls replay_active: false, replay_speed: @default_replay_speed, @@ -57,10 +60,12 @@ defmodule AprsWeb.MapLive.CallsignView do ) if connected?(socket) do + IO.puts("CallsignView: Socket connected, subscribing to messages") Endpoint.subscribe("aprs_messages") # Load recent packets for this callsign socket = load_callsign_packets(socket, normalized_callsign) + IO.puts("CallsignView: Loaded packets, map_center: #{inspect(socket.assigns.map_center)}") # Schedule regular cleanup of old packets from the map Process.send_after(self(), :cleanup_old_packets, 60_000) @@ -69,6 +74,7 @@ defmodule AprsWeb.MapLive.CallsignView do {:ok, socket} else + IO.puts("CallsignView: Socket not connected") {:ok, socket} end end @@ -137,13 +143,16 @@ defmodule AprsWeb.MapLive.CallsignView do end def handle_event("map_ready", _params, socket) do + IO.puts("CallsignView: Map ready event received") socket = assign(socket, map_ready: true) # Auto-start replay if it hasn't been started yet socket = if socket.assigns.replay_started do + IO.puts("CallsignView: Replay already started") socket else + IO.puts("CallsignView: Starting historical replay") socket = start_historical_replay(socket) assign(socket, replay_started: true, replay_active: true) end @@ -152,13 +161,16 @@ defmodule AprsWeb.MapLive.CallsignView do socket = if socket.assigns.pending_geolocation do location = socket.assigns.pending_geolocation + IO.puts("CallsignView: Zooming to pending geolocation: #{inspect(location)}") push_event(socket, "zoom_to_location", %{lat: location.lat, lng: location.lng, zoom: 12}) else # If we have a last known position, zoom to it if socket.assigns.last_known_position do pos = socket.assigns.last_known_position + IO.puts("CallsignView: Zooming to last known position: #{inspect(pos)}") push_event(socket, "zoom_to_location", %{lat: pos.lat, lng: pos.lng, zoom: 12}) else + IO.puts("CallsignView: No position data to zoom to") socket end end @@ -367,16 +379,31 @@ defmodule AprsWeb.MapLive.CallsignView do + +
+
+
Map Debug Panel
+ +
+ Map Dimensions:
+ Checking... +
+ +
+ Leaflet Library:
+ {if @debug_info.leaflet_loaded, do: "✓ Loaded", else: "⏳ Loading..."} +
+ +
+ Phoenix Hook:
+ {if @debug_info.hook_mounted, do: "✓ Mounted", else: "⏳ Waiting..."} +
+ +
+ Map Initialization:
+ {if @debug_info.map_initialized, do: "✓ Initialized", else: "⏳ Waiting..."} +
+ +
+ Map Ready:
+ {if @debug_info.map_ready, do: "✓ Ready", else: "⏳ Waiting..."} +
+ + <%= if length(@debug_info.errors) > 0 do %> +
+ Errors: +
+ <%= for error <- @debug_info.errors do %> + • {error}
+ <% end %> +
+ <% end %> + +
+ Test Actions:
+ + + + +
+ +
+ Console Output: +
+
+
+
+ +
+ Instructions: +
1. Open browser dev tools (F12)
2. Check Console tab for errors
+ 3. Watch this panel for status updates
4. Use test buttons to debug issues +
+
+ +
+
+
+
+
+ + + """ + end +end diff --git a/lib/aprs_web/router.ex b/lib/aprs_web/router.ex index 12e9015..c79b3dc 100644 --- a/lib/aprs_web/router.ex +++ b/lib/aprs_web/router.ex @@ -31,6 +31,8 @@ defmodule AprsWeb.Router do live "/enhanced", MapLive.Enhanced, :index get "/old", PageController, :map live "/status", StatusLive.Index, :index + live "/debug_map", MapLive.Debug, :index + get "/test_map", PageController, :test_map live "/packets", PacketsLive.Index, :index live "/:callsign", MapLive.CallsignView, :index