historical lines
This commit is contained in:
parent
8fdafcf01e
commit
cd011b9667
3 changed files with 217 additions and 18 deletions
|
|
@ -48,16 +48,23 @@ export class TrailManager {
|
|||
if (!trailState) {
|
||||
trailState = { positions: [] };
|
||||
this.trails.set(baseCallsign, trailState);
|
||||
console.log(`TrailManager: Created new trail for ${baseCallsign}`);
|
||||
}
|
||||
|
||||
// Only add if position is different from the last one (more than ~1 meter)
|
||||
const lastPos = trailState.positions[trailState.positions.length - 1];
|
||||
if (
|
||||
!lastPos ||
|
||||
Math.abs(lastPos.lat - lat) > 0.00001 ||
|
||||
Math.abs(lastPos.lng - lng) > 0.00001
|
||||
) {
|
||||
// Check if this position already exists (avoid duplicates)
|
||||
const existingPos = trailState.positions.find(
|
||||
(pos) =>
|
||||
Math.abs(pos.lat - lat) < 0.00001 &&
|
||||
Math.abs(pos.lng - lng) < 0.00001 &&
|
||||
Math.abs(pos.timestamp - timestamp) < 1000, // Within 1 second
|
||||
);
|
||||
|
||||
if (!existingPos) {
|
||||
// Add new position
|
||||
trailState.positions.push({ lat, lng, timestamp });
|
||||
|
||||
// Sort positions by timestamp to maintain chronological order
|
||||
trailState.positions.sort((a, b) => a.timestamp - b.timestamp);
|
||||
}
|
||||
|
||||
// For historical dots, keep all positions. For live positions, filter by time.
|
||||
|
|
@ -79,11 +86,15 @@ export class TrailManager {
|
|||
// Ensure markerId is a string
|
||||
const id = typeof markerId === "string" ? markerId : String(markerId);
|
||||
|
||||
// Extract base callsign from various ID formats
|
||||
// For historical markers like "hist_CALLSIGN_123", extract the CALLSIGN part
|
||||
if (id.startsWith("hist_")) {
|
||||
// Remove hist_ prefix and any trailing index
|
||||
return id.replace(/^hist_/, "").replace(/_\d+$/, "");
|
||||
// Remove hist_ prefix and any trailing numeric index
|
||||
const withoutPrefix = id.replace(/^hist_/, "");
|
||||
// Remove trailing _digits pattern
|
||||
return withoutPrefix.replace(/_\d+$/, "");
|
||||
}
|
||||
|
||||
// For regular callsigns or IDs, return as-is
|
||||
return id;
|
||||
}
|
||||
|
||||
|
|
@ -113,6 +124,10 @@ export class TrailManager {
|
|||
className: "historical-trail-line",
|
||||
}).addTo(this.trailLayer);
|
||||
|
||||
console.log(
|
||||
`TrailManager: Created trail line for ${baseCallsign} with ${trailState.positions.length} positions`,
|
||||
);
|
||||
|
||||
// Don't create additional dots here since historical positions are now shown as markers
|
||||
trailState.dots = [];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -499,9 +499,73 @@ let MapAPRSMap = {
|
|||
|
||||
// Handle new packets from LiveView
|
||||
self.handleEvent("new_packet", (data: MarkerData) => {
|
||||
// Check if there's already a marker for this callsign
|
||||
const incomingCallsign = data.callsign_group || data.callsign || data.id;
|
||||
|
||||
if (incomingCallsign) {
|
||||
// Find existing marker for this callsign - check both live and historical markers
|
||||
let existingMarkerId: string | null = null;
|
||||
let existingCallsign: string | null = null;
|
||||
|
||||
self.markerStates!.forEach((state, id) => {
|
||||
// Extract callsign from various sources
|
||||
const stateCallsign =
|
||||
state.callsign_group ||
|
||||
(typeof id === "string" && !id.startsWith("hist_") ? id : null) ||
|
||||
(typeof id === "string" && id.startsWith("hist_")
|
||||
? id.replace(/^hist_/, "").replace(/_\d+$/, "")
|
||||
: null);
|
||||
|
||||
// Match callsigns (only consider non-historical markers for conversion)
|
||||
if (stateCallsign === incomingCallsign && !state.historical) {
|
||||
existingMarkerId = id;
|
||||
existingCallsign = stateCallsign;
|
||||
}
|
||||
});
|
||||
|
||||
// If we found an existing live marker, convert it to a historical dot
|
||||
if (existingMarkerId && existingCallsign) {
|
||||
const existingMarker = self.markers!.get(existingMarkerId);
|
||||
const existingState = self.markerStates!.get(existingMarkerId);
|
||||
|
||||
if (existingMarker && existingState) {
|
||||
// Create historical dot data from existing marker
|
||||
const historicalData = {
|
||||
id: `hist_${existingCallsign}_${Date.now()}`,
|
||||
lat: existingState.lat,
|
||||
lng: existingState.lng,
|
||||
callsign: existingCallsign,
|
||||
callsign_group: existingCallsign,
|
||||
symbol_table_id: existingState.symbol_table,
|
||||
symbol_code: existingState.symbol_code,
|
||||
historical: true,
|
||||
is_most_recent_for_callsign: false,
|
||||
timestamp: data.timestamp
|
||||
? typeof data.timestamp === "string"
|
||||
? new Date(data.timestamp).getTime() - 1000
|
||||
: data.timestamp - 1000
|
||||
: Date.now() - 1000,
|
||||
popup: existingState.popup,
|
||||
};
|
||||
|
||||
// Remove the existing marker (this will also clean up its trail reference)
|
||||
self.removeMarker(existingMarkerId);
|
||||
|
||||
// Add the historical dot marker
|
||||
self.addMarker({
|
||||
...historicalData,
|
||||
popup: self.buildPopupContent(historicalData),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add the new marker with proper callsign grouping
|
||||
self.addMarker({
|
||||
...data,
|
||||
historical: false,
|
||||
is_most_recent_for_callsign: true,
|
||||
callsign_group: data.callsign_group || data.callsign || incomingCallsign, // Ensure callsign_group is set for trail grouping
|
||||
popup: self.buildPopupContent(data),
|
||||
openPopup: true,
|
||||
});
|
||||
|
|
@ -683,7 +747,10 @@ let MapAPRSMap = {
|
|||
? new Date(data.timestamp).getTime()
|
||||
: data.timestamp
|
||||
: Date.now();
|
||||
self.trailManager.addPosition(data.id, lat, lng, timestamp, isHistoricalDot);
|
||||
// Use callsign_group for proper trail grouping - prioritize callsign_group, then callsign, then id
|
||||
const trailId = data.callsign_group || data.callsign || data.id;
|
||||
|
||||
self.trailManager.addPosition(trailId, lat, lng, timestamp, isHistoricalDot);
|
||||
}
|
||||
|
||||
if (!positionChanged && !dataChanged) {
|
||||
|
|
@ -738,7 +805,8 @@ let MapAPRSMap = {
|
|||
popup: data.popup,
|
||||
historical: data.historical,
|
||||
is_most_recent_for_callsign: data.is_most_recent_for_callsign,
|
||||
callsign_group: data.callsign_group,
|
||||
callsign_group: data.callsign_group || data.callsign,
|
||||
callsign: data.callsign,
|
||||
});
|
||||
|
||||
// Initialize trail for new marker - always add to trail for line drawing
|
||||
|
|
@ -749,7 +817,10 @@ let MapAPRSMap = {
|
|||
? new Date(data.timestamp).getTime()
|
||||
: data.timestamp
|
||||
: Date.now();
|
||||
self.trailManager.addPosition(data.id, lat, lng, timestamp, isHistoricalDot);
|
||||
// Use callsign_group for proper trail grouping - prioritize callsign_group, then callsign, then id
|
||||
const trailId = data.callsign_group || data.callsign || data.id;
|
||||
|
||||
self.trailManager.addPosition(trailId, lat, lng, timestamp, isHistoricalDot);
|
||||
}
|
||||
|
||||
// Open popup if requested
|
||||
|
|
@ -764,15 +835,18 @@ let MapAPRSMap = {
|
|||
const markerId = typeof id === "string" ? id : String(id);
|
||||
|
||||
const marker = self.markers!.get(markerId);
|
||||
const markerState = self.markerStates!.get(markerId);
|
||||
|
||||
if (marker) {
|
||||
self.markerLayer!.removeLayer(marker);
|
||||
self.markers!.delete(markerId);
|
||||
self.markerStates!.delete(markerId);
|
||||
}
|
||||
|
||||
// Remove trail
|
||||
// Remove trail - use callsign_group for proper trail identification
|
||||
if (self.trailManager) {
|
||||
self.trailManager.removeTrail(markerId);
|
||||
const trailId = markerState?.callsign_group || markerState?.callsign || markerId;
|
||||
self.trailManager.removeTrail(trailId);
|
||||
}
|
||||
},
|
||||
|
||||
|
|
@ -800,7 +874,9 @@ let MapAPRSMap = {
|
|||
? new Date(data.timestamp).getTime()
|
||||
: data.timestamp
|
||||
: Date.now();
|
||||
self.trailManager.addPosition(data.id, lat, lng, timestamp, isHistoricalDot);
|
||||
// Use callsign_group for proper trail grouping - prioritize callsign_group, then callsign, then id
|
||||
const trailId = data.callsign_group || data.callsign || data.id;
|
||||
self.trailManager.addPosition(trailId, lat, lng, timestamp, isHistoricalDot);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -237,7 +237,8 @@ defmodule AprsWeb.MapLive.Index do
|
|||
|
||||
@spec process_bounds_update(map(), Socket.t()) :: Socket.t()
|
||||
defp process_bounds_update(map_bounds, socket) do
|
||||
Logger.debug("process_bounds_update: (no DB query) for bounds #{inspect(map_bounds)}")
|
||||
Logger.debug("process_bounds_update: Loading historical packets for bounds #{inspect(map_bounds)}")
|
||||
|
||||
# Remove out-of-bounds packets and markers immediately
|
||||
new_visible_packets =
|
||||
socket.assigns.visible_packets
|
||||
|
|
@ -259,7 +260,13 @@ defmodule AprsWeb.MapLive.Index do
|
|||
end)
|
||||
end
|
||||
|
||||
# Only update visible_packets with the packets still in bounds
|
||||
# Clear existing historical packets
|
||||
socket = push_event(socket, "clear_historical_packets", %{})
|
||||
|
||||
# Load historical packets for the new bounds
|
||||
socket = load_historical_packets_for_bounds(socket, map_bounds)
|
||||
|
||||
# Update map bounds and visible packets
|
||||
assign(socket, map_bounds: map_bounds, visible_packets: new_visible_packets)
|
||||
end
|
||||
|
||||
|
|
@ -746,6 +753,107 @@ defmodule AprsWeb.MapLive.Index do
|
|||
Enum.sort_by(packets, fn packet -> packet.received_at end)
|
||||
end
|
||||
|
||||
@spec load_historical_packets_for_bounds(Socket.t(), map()) :: Socket.t()
|
||||
defp load_historical_packets_for_bounds(socket, map_bounds) do
|
||||
# Get time range for historical data
|
||||
now = DateTime.utc_now()
|
||||
one_hour_ago = DateTime.add(now, -60 * 60, :second)
|
||||
|
||||
# Convert map bounds to the format expected by the database query
|
||||
bounds = [
|
||||
map_bounds.west,
|
||||
map_bounds.south,
|
||||
map_bounds.east,
|
||||
map_bounds.north
|
||||
]
|
||||
|
||||
# Fetch historical packets with position data within the current map bounds
|
||||
historical_packets = fetch_historical_packets(bounds, one_hour_ago, now)
|
||||
|
||||
if Enum.empty?(historical_packets) do
|
||||
# No historical packets found
|
||||
socket
|
||||
else
|
||||
# Group packets by callsign-ssid and process all positions
|
||||
packet_data_list =
|
||||
historical_packets
|
||||
|> Enum.group_by(&generate_callsign/1)
|
||||
|> Enum.flat_map(fn {callsign, packets} ->
|
||||
# Sort packets by inserted_at to identify the most recent
|
||||
sorted_packets =
|
||||
Enum.sort_by(
|
||||
packets,
|
||||
fn packet ->
|
||||
case packet.inserted_at do
|
||||
%NaiveDateTime{} = naive_dt ->
|
||||
DateTime.from_naive!(naive_dt, "Etc/UTC")
|
||||
|
||||
%DateTime{} = dt ->
|
||||
dt
|
||||
|
||||
_other ->
|
||||
DateTime.utc_now()
|
||||
end
|
||||
end,
|
||||
{:desc, DateTime}
|
||||
)
|
||||
|
||||
# Filter out packets with unchanged positions
|
||||
unique_position_packets = filter_unique_positions(sorted_packets)
|
||||
|
||||
# Process all packets with unique positions, marking which is the most recent
|
||||
unique_position_packets
|
||||
|> Enum.with_index()
|
||||
|> Enum.map(fn {packet, index} ->
|
||||
case build_packet_data(packet) do
|
||||
nil ->
|
||||
nil
|
||||
|
||||
packet_data ->
|
||||
# Generate a unique ID for this historical packet
|
||||
packet_id =
|
||||
"hist_#{if Map.has_key?(packet, :id), do: packet.id, else: System.unique_integer([:positive])}_#{index}"
|
||||
|
||||
is_most_recent = index == 0
|
||||
|
||||
packet_data
|
||||
|> Map.put("id", packet_id)
|
||||
|> Map.put("is_historical", true)
|
||||
|> Map.put("is_most_recent_for_callsign", is_most_recent)
|
||||
|> Map.put("callsign_group", callsign)
|
||||
|> Map.put(
|
||||
"timestamp",
|
||||
case packet.inserted_at do
|
||||
%NaiveDateTime{} = naive_dt ->
|
||||
DateTime.to_unix(DateTime.from_naive!(naive_dt, "Etc/UTC"), :millisecond)
|
||||
|
||||
%DateTime{} = dt ->
|
||||
DateTime.to_unix(dt, :millisecond)
|
||||
|
||||
_other ->
|
||||
DateTime.to_unix(DateTime.utc_now(), :millisecond)
|
||||
end
|
||||
)
|
||||
end
|
||||
end)
|
||||
|> Enum.filter(& &1)
|
||||
end)
|
||||
|
||||
# Send all historical packets at once
|
||||
socket = push_event(socket, "add_historical_packets", %{packets: packet_data_list})
|
||||
|
||||
# Store historical packets in assigns for reference
|
||||
historical_packets_map =
|
||||
packet_data_list
|
||||
|> Enum.zip(historical_packets)
|
||||
|> Enum.reduce(%{}, fn {packet_data, packet}, acc ->
|
||||
Map.put(acc, packet_data["id"], packet)
|
||||
end)
|
||||
|
||||
assign(socket, historical_packets: historical_packets_map)
|
||||
end
|
||||
end
|
||||
|
||||
@spec within_bounds?(map() | struct(), map()) :: boolean()
|
||||
defp within_bounds?(packet, bounds) do
|
||||
{lat, lon, _data_extended} = MapHelpers.get_coordinates(packet)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue