diff --git a/assets/js/features/trail_manager.ts b/assets/js/features/trail_manager.ts index 4810807..814612b 100644 --- a/assets/js/features/trail_manager.ts +++ b/assets/js/features/trail_manager.ts @@ -105,6 +105,21 @@ export class TrailManager { ) { if (!this.showTrails) return; + // Validate coordinates before processing + if ( + typeof lat !== 'number' || + typeof lng !== 'number' || + !isFinite(lat) || + !isFinite(lng) || + lat < -90 || + lat > 90 || + lng < -180 || + lng > 180 + ) { + console.warn("Invalid coordinates provided to addPosition:", { markerId, lat, lng, timestamp }); + return; + } + // Extract base callsign from markerId to group positions by callsign const baseCallsign = this.extractBaseCallsign(markerId); @@ -253,29 +268,53 @@ export class TrailManager { trailState.dots = []; } - // Create new trail if we have at least 2 positions + // Create new trail if we have at least 2 positions with valid coordinates if (trailState.positions.length >= 2) { - const latLngs: [number, number][] = trailState.positions.map((pos) => [pos.lat, pos.lng]); + // Filter out positions with invalid coordinates and create coordinate pairs + const latLngs: [number, number][] = trailState.positions + .filter((pos) => { + // Validate coordinates are finite numbers within valid ranges + return ( + pos && + typeof pos.lat === 'number' && + typeof pos.lng === 'number' && + isFinite(pos.lat) && + isFinite(pos.lng) && + pos.lat >= -90 && + pos.lat <= 90 && + pos.lng >= -180 && + pos.lng <= 180 + ); + }) + .map((pos) => [pos.lat, pos.lng]); - // Assign color if not already assigned - if (!trailState.color) { - trailState.color = this.assignTrailColor(baseCallsign, trailState.positions); + // Only create trail if we still have at least 2 valid positions after filtering + if (latLngs.length >= 2) { + // Assign color if not already assigned + if (!trailState.color) { + trailState.color = this.assignTrailColor(baseCallsign, trailState.positions); + } + + // Create polyline with assigned color + // For historical positions (immediate=true), use higher opacity for better visibility + const polylineOptions: PolylineOptions = { + color: trailState.color, + weight: 3, + opacity: immediate ? 0.9 : 0.8, + smoothFactor: 1, + lineCap: "round", + lineJoin: "round", + className: "historical-trail-line", + }; + + try { + trailState.trail = L.polyline(latLngs, polylineOptions).addTo(this.trailLayer); + } catch (error) { + console.error("Error creating trail polyline for", baseCallsign, ":", error); + console.error("Invalid coordinates:", latLngs); + } } - // Create polyline with assigned color - // For historical positions (immediate=true), use higher opacity for better visibility - const polylineOptions: PolylineOptions = { - color: trailState.color, - weight: 3, - opacity: immediate ? 0.9 : 0.8, - smoothFactor: 1, - lineCap: "round", - lineJoin: "round", - className: "historical-trail-line", - }; - - trailState.trail = L.polyline(latLngs, polylineOptions).addTo(this.trailLayer); - // Don't create additional dots here since historical positions are now shown as markers trailState.dots = []; } diff --git a/assets/js/map.ts b/assets/js/map.ts index 206d9fb..c95b5a9 100644 --- a/assets/js/map.ts +++ b/assets/js/map.ts @@ -1075,11 +1075,15 @@ let MapAPRSMap = { // Add markers in chronological order sortedPackets.forEach((packet) => { - self.addMarker({ - ...packet, - historical: true, - popup: packet.popup || self.buildPopupContent(packet), - }); + try { + self.addMarker({ + ...packet, + historical: true, + popup: packet.popup || self.buildPopupContent(packet), + }); + } catch (error) { + console.error("Error adding historical packet:", error, packet); + } }); }); } @@ -1118,11 +1122,15 @@ let MapAPRSMap = { // Add markers in chronological order sortedPackets.forEach((packet) => { - self.addMarker({ - ...packet, - historical: true, - popup: packet.popup || self.buildPopupContent(packet), - }); + try { + self.addMarker({ + ...packet, + historical: true, + popup: packet.popup || self.buildPopupContent(packet), + }); + } catch (error) { + console.error("Error adding historical packet:", error, packet); + } }); }); } @@ -1429,7 +1437,7 @@ let MapAPRSMap = { // Validate coordinates if (!isValidCoordinate(lat, lng)) { - console.warn("Invalid coordinates:", lat, lng); + console.warn("Invalid coordinates for marker:", { id: data.id, lat, lng, callsign: data.callsign }); return; } diff --git a/lib/aprsme/cache.ex b/lib/aprsme/cache.ex index edda431..9c32547 100644 --- a/lib/aprsme/cache.ex +++ b/lib/aprsme/cache.ex @@ -98,7 +98,7 @@ defmodule Aprsme.Cache do opts ttl_ms when is_integer(ttl_ms) and ttl_ms > 0 -> - ttl_seconds = Integer.ceil_div(ttl_ms, 1000) |> max(1) + ttl_seconds = ttl_ms |> Integer.ceil_div(1000) |> max(1) Keyword.put(opts, :ttl, ttl_seconds) ttl_ms when is_integer(ttl_ms) -> diff --git a/lib/aprsme_web/live/map_live/data_builder.ex b/lib/aprsme_web/live/map_live/data_builder.ex index db2a654..28f3592 100644 --- a/lib/aprsme_web/live/map_live/data_builder.ex +++ b/lib/aprsme_web/live/map_live/data_builder.ex @@ -50,9 +50,19 @@ defmodule AprsmeWeb.MapLive.DataBuilder do {lat, lon, data_extended} = MapHelpers.get_coordinates(packet) callsign = generate_callsign(packet) - if lat && lon && callsign != "" && callsign != nil do - packet_data = build_packet_map(packet, lat, lon, data_extended, locale) + # Validate coordinates and callsign before building packet data + if valid_coordinates?(lat, lon) && callsign != "" && callsign != nil do + packet_data = build_packet_map(packet, to_float(lat), to_float(lon), data_extended, locale) Map.put(packet_data, "is_most_recent_for_callsign", is_most_recent_for_callsign) + else + # Log invalid data for debugging + if not valid_coordinates?(lat, lon) do + require Logger + + Logger.debug("Invalid coordinates in packet #{get_packet_id(packet)}: lat=#{inspect(lat)}, lon=#{inspect(lon)}") + end + + nil end end @@ -81,7 +91,8 @@ defmodule AprsmeWeb.MapLive.DataBuilder do # Build minimal packet data without calling expensive build_packet_data {lat, lon, _} = get_coordinates(packet) - if lat && lon do + # Validate coordinates are numeric and within valid ranges + if valid_coordinates?(lat, lon) do # Use get_packet_field to get symbol information properly (includes data_extended fallback) symbol_table_id = get_packet_field(packet, :symbol_table_id, "/") symbol_code = get_packet_field(packet, :symbol_code, ">") @@ -97,8 +108,8 @@ defmodule AprsmeWeb.MapLive.DataBuilder do %{ "id" => if(is_most_recent, do: "current_#{get_packet_id(packet)}", else: "hist_#{get_packet_id(packet)}"), - "lat" => lat, - "lng" => lon, + "lat" => to_float(lat), + "lng" => to_float(lon), "callsign" => get_packet_field(packet, :sender, ""), "symbol_table_id" => symbol_table_id, "symbol_code" => symbol_code, @@ -110,9 +121,24 @@ defmodule AprsmeWeb.MapLive.DataBuilder do "path" => get_packet_field(packet, :path, ""), "popup" => build_simple_popup(packet, has_weather) } + else + # Log invalid coordinates for debugging + require Logger + + Logger.debug("Invalid coordinates in packet #{get_packet_id(packet)}: lat=#{inspect(lat)}, lon=#{inspect(lon)}") + nil end end + # Validate coordinates are numeric and within valid ranges + @spec valid_coordinates?(any(), any()) :: boolean() + defp valid_coordinates?(lat, lon) when is_number(lat) and is_number(lon) do + lat >= -90 and lat <= 90 and lon >= -180 and lon <= 180 and + :math.is_finite(lat) and :math.is_finite(lon) + end + + defp valid_coordinates?(_, _), do: false + @doc """ Build packet data list for historical display. Moved from historical_loader.ex. diff --git a/lib/aprsme_web/live/map_live/historical_loader.ex b/lib/aprsme_web/live/map_live/historical_loader.ex index 50dd8bd..5495408 100644 --- a/lib/aprsme_web/live/map_live/historical_loader.ex +++ b/lib/aprsme_web/live/map_live/historical_loader.ex @@ -216,7 +216,23 @@ defmodule AprsmeWeb.MapLive.HistoricalLoader do # Process this batch and send to frontend packet_data_list = try do - DataBuilder.build_packet_data_list(historical_packets) + # Filter out packets with invalid coordinates before processing + valid_packets = + Enum.filter(historical_packets, fn packet -> + {lat, lon, _} = AprsmeWeb.Live.Shared.CoordinateUtils.get_coordinates(packet) + + is_number(lat) and is_number(lon) and + lat >= -90 and lat <= 90 and lon >= -180 and lon <= 180 and + :math.is_finite(lat) and :math.is_finite(lon) + end) + + if length(valid_packets) < length(historical_packets) do + Logger.debug( + "Filtered out #{length(historical_packets) - length(valid_packets)} packets with invalid coordinates" + ) + end + + DataBuilder.build_packet_data_list(valid_packets) rescue e -> require Logger