Merge pull request #14 from aprsme/fix-trail-line-connections

Fix trail lines not connecting historical positions
This commit is contained in:
Graham McIntire 2025-07-21 08:39:17 -05:00 committed by GitHub
commit 8906e218dd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 96 additions and 10 deletions

View file

@ -48,23 +48,18 @@ jobs:
otp: ["27.3.4"]
elixir: ["1.18.4"]
steps:
# Step: Setup Elixir + Erlang image as the base.
- name: Set up Elixir
# Step: Setup Elixir + Erlang + Gleam using erlef/setup-beam
- name: Set up Elixir, Erlang, and Gleam
uses: erlef/setup-beam@v1
with:
otp-version: ${{matrix.otp}}
elixir-version: ${{matrix.elixir}}
gleam-version: "1.5.1"
# Step: Check out the code.
- name: Checkout code
uses: actions/checkout@v4
# Step: Install Gleam
- name: Set up Gleam
uses: gleam-lang/setup-gleam@v1
with:
gleam-version: "1.5.1"
# Step: Install mix_gleam archive
- name: Install mix_gleam
run: mix archive.install hex mix_gleam 0.6.2 --force

View file

@ -78,6 +78,24 @@ export class TrailManager {
}
}
setTrailDuration(durationHours: number) {
this.trailDuration = durationHours * 60 * 60 * 1000; // Convert hours to milliseconds
// Clean up existing trails based on new duration
const cutoffTime = Date.now() - this.trailDuration;
this.trails.forEach((trailState, baseCallsign) => {
// Filter positions based on new duration (skip historical dots)
trailState.positions = trailState.positions.filter((pos) => {
const posTimestamp = typeof pos.timestamp === "string" ? new Date(pos.timestamp).getTime() : pos.timestamp;
return posTimestamp >= cutoffTime;
});
// Update trail visualization
this.updateTrailVisualization(baseCallsign, trailState, true);
});
}
addPosition(
markerId: string,
lat: number,

View file

@ -663,6 +663,13 @@ let MapAPRSMap = {
}
});
// Handle trail duration updates from LiveView
self.handleEvent("update_trail_duration", (data: { duration_hours: number }) => {
if (self.trailManager) {
self.trailManager.setTrailDuration(data.duration_hours);
}
});
// Handle new packets from LiveView
self.handleEvent("new_packet", (data: MarkerData) => {
try {

View file

@ -34,8 +34,27 @@ export function parseTimestamp(timestamp: string | number | Date | undefined): n
/**
* Get trail ID from marker data
*/
export function getTrailId(data: { callsign_group?: string; callsign?: string; id: string }): string {
return data.callsign_group || data.callsign || data.id;
export function getTrailId(data: { callsign_group?: string; callsign?: string; id: string | number }): string {
// Prioritize callsign_group and callsign over extracting from ID
if (data.callsign_group) {
return data.callsign_group;
}
if (data.callsign) {
return data.callsign;
}
// For historical markers like "hist_CALLSIGN_123", extract the base callsign
const id = String(data.id);
if (id.startsWith("hist_")) {
// Remove hist_ prefix and any trailing numeric index
const withoutPrefix = id.replace(/^hist_/, "");
// Remove trailing _digits pattern
return withoutPrefix.replace(/_\d+$/, "");
}
// For regular IDs, return as string
return String(data.id);
}
/**

View file

@ -360,6 +360,9 @@ defmodule AprsmeWeb.MapLive.Index do
socket = assign(socket, trail_duration: duration, packet_age_threshold: new_threshold)
# Update client-side TrailManager with new duration
socket = push_event(socket, "update_trail_duration", %{duration_hours: hours})
# Trigger cleanup to remove packets that are now outside the new duration
send(self(), :cleanup_old_packets)

View file

@ -62,6 +62,50 @@ describe('map_helpers', () => {
};
expect(getTrailId(data)).toBe('ID-1');
});
test('extracts base callsign from historical ID format', () => {
const data = {
id: 'hist_MYCALL_123'
};
expect(getTrailId(data)).toBe('MYCALL');
});
test('handles historical ID with complex callsign', () => {
const data = {
id: 'hist_KD8ABC-9_456'
};
expect(getTrailId(data)).toBe('KD8ABC-9');
});
test('prioritizes callsign_group over historical ID extraction', () => {
const data = {
callsign_group: 'GROUP-1',
id: 'hist_MYCALL_123'
};
expect(getTrailId(data)).toBe('GROUP-1');
});
test('prioritizes callsign over historical ID extraction', () => {
const data = {
callsign: 'REALCALL',
id: 'hist_MYCALL_123'
};
expect(getTrailId(data)).toBe('REALCALL');
});
test('handles numeric ID', () => {
const data = {
id: 12345
};
expect(getTrailId(data)).toBe('12345');
});
test('handles numeric historical ID', () => {
const data = {
id: 'hist_MYCALL_12345'
};
expect(getTrailId(data)).toBe('MYCALL');
});
});
describe('saveMapState', () => {