aprs.me/assets/js/map_helpers.ts
Graham McIntire cf78298f46
Fix trail lines not connecting historical positions
When changing trail duration and historical data settings, historical positions
would show up on the map but trail lines wouldn't connect them. This was caused
by a mismatch between trail ID generation and callsign extraction.

## Changes

- Fix getTrailId() to properly extract base callsigns from historical marker IDs
- Add setTrailDuration() method to TrailManager for dynamic duration updates
- Add LiveView event to synchronize trail duration changes with client
- Add comprehensive tests for historical ID handling

## Problem
Historical markers have IDs like "hist_CALLSIGN_123" but getTrailId() was
returning the full ID while TrailManager.extractBaseCallsign() extracted just
"CALLSIGN", creating separate trails instead of connected ones.

## Solution
Updated getTrailId() to extract base callsigns from historical IDs using the
same logic as TrailManager, ensuring all positions from the same station use
the same trail ID and connect properly.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-21 08:31:16 -05:00

135 lines
No EOL
3.5 KiB
TypeScript

// Helper functions for map.ts to reduce code duplication
import type * as L from 'leaflet';
import type { BaseEventPayload, PushEventFunction } from './types/events';
import type { LiveSocket } from './types/map';
export interface MapState {
lat: number;
lng: number;
zoom: number;
}
export interface BoundsData {
north: number;
south: number;
east: number;
west: number;
}
/**
* Parse timestamp to milliseconds
*/
export function parseTimestamp(timestamp: string | number | Date | undefined): number {
if (!timestamp) return Date.now();
if (typeof timestamp === "number") {
return timestamp;
} else if (typeof timestamp === "string") {
return new Date(timestamp).getTime();
}
return Date.now();
}
/**
* Get trail ID from marker data
*/
export function getTrailId(data: { callsign_group?: string; callsign?: string; id: string }): 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-is
return data.id;
}
/**
* Save map state to localStorage and send to server
*/
// PushEventFunction is now imported from types/events
export function saveMapState(map: L.Map, pushEvent: PushEventFunction) {
if (!map || !pushEvent) {
console.warn("saveMapState called with invalid map or pushEvent");
return;
}
try {
const center = map.getCenter();
const zoom = map.getZoom();
// Truncate lat/lng to 5 decimal places for URL
const truncatedLat = Math.round(center.lat * 100000) / 100000;
const truncatedLng = Math.round(center.lng * 100000) / 100000;
// Always save to localStorage, even if LiveView is disconnected
// Temporarily disabled - using server-side geolocation instead
/*
localStorage.setItem(
"aprs_map_state",
JSON.stringify({ lat: truncatedLat, lng: truncatedLng, zoom }),
);
*/
// Send combined map state update to server for URL and bounds updating
const payload = {
center: { lat: truncatedLat, lng: truncatedLng },
zoom: zoom,
bounds: {
north: map.getBounds().getNorth(),
south: map.getBounds().getSouth(),
east: map.getBounds().getEast(),
west: map.getBounds().getWest(),
}
};
// Use safePushEvent to handle disconnected state
safePushEvent(pushEvent, "update_map_state", payload);
} catch (error) {
console.error("Error in saveMapState:", error);
}
}
/**
* Safely push event to LiveView
*/
export function safePushEvent<T extends BaseEventPayload = BaseEventPayload>(pushEvent: PushEventFunction | undefined, event: string, payload: T): boolean {
if (!pushEvent || typeof pushEvent !== 'function') {
return false;
}
try {
pushEvent(event, payload);
return true;
} catch (e) {
return false;
}
}
/**
* Check if LiveView socket is available
*/
export function isLiveViewConnected(): boolean {
return typeof window !== 'undefined' && !!window.liveSocket;
}
/**
* Get LiveView socket
*/
export function getLiveSocket(): LiveSocket | null {
return typeof window !== 'undefined' ? window.liveSocket || null : null;
}