aprs.me/assets/js/hooks/info_map.js
Graham McIntire 85c3b3d9e7
Improve info page live updates and fix APRS parser
- Fixed info page not receiving live updates by implementing callsign-specific PubSub topics
- Added automatic time counter for "Last Position" and other timestamps using JavaScript hook
- Optimized map updates to only move marker instead of reloading entire map
- Fixed APRS parser to properly handle [000/000/A=altitude format in comments
- Removed packet caching on info page for real-time updates

The info page now properly subscribes to updates for the specific callsign being viewed,
timestamps automatically count up without page refresh, and the map smoothly animates
marker position changes instead of flickering with full reloads.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-28 15:14:33 -05:00

138 lines
No EOL
3.9 KiB
JavaScript

// Simple map hook for displaying a single station on the info page
export const InfoMap = {
mounted() {
this.initializeMap();
},
updated() {
// When the element updates, check if we need to update the marker
const lat = parseFloat(this.el.dataset.lat);
const lon = parseFloat(this.el.dataset.lon);
const symbolHtml = this.el.dataset.symbolHtml;
// Validate coordinates
if (isNaN(lat) || isNaN(lon)) {
return;
}
// If map doesn't exist yet, initialize it
if (!this.map) {
this.initializeMap();
return;
}
// Update marker position if it changed
if (this.marker) {
const currentPos = this.marker.getLatLng();
if (currentPos.lat !== lat || currentPos.lng !== lon) {
// Animate the marker to the new position
this.marker.setLatLng([lat, lon]);
// Update the popup content
const callsign = this.el.dataset.callsign;
this.marker.setPopupContent(`<strong>${callsign}</strong><br/>Lat: ${lat.toFixed(6)}<br/>Lon: ${lon.toFixed(6)}`);
// Optionally pan the map to the new position with animation
this.map.panTo([lat, lon], { animate: true, duration: 1 });
}
// Update marker icon if symbol changed
if (symbolHtml !== this.lastSymbolHtml) {
const markerIcon = this.createMarkerIcon(symbolHtml);
this.marker.setIcon(markerIcon);
this.lastSymbolHtml = symbolHtml;
}
}
},
initializeMap() {
// Check if Leaflet is available
if (typeof L === "undefined") {
console.error("Leaflet not loaded for InfoMap");
return;
}
// Get data from element attributes
const lat = parseFloat(this.el.dataset.lat);
const lon = parseFloat(this.el.dataset.lon);
const zoom = parseInt(this.el.dataset.zoom) || 13;
const callsign = this.el.dataset.callsign;
const symbolHtml = this.el.dataset.symbolHtml;
// Validate coordinates
if (isNaN(lat) || isNaN(lon)) {
console.error("Invalid coordinates for InfoMap");
return;
}
// Initialize the map
try {
this.map = L.map(this.el, {
center: [lat, lon],
zoom: zoom,
scrollWheelZoom: false, // Disable scroll zoom for embedded maps
zoomControl: true,
attributionControl: true
});
// Add tile layer
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>',
maxZoom: 19
}).addTo(this.map);
// Create marker icon
const markerIcon = this.createMarkerIcon(symbolHtml);
this.lastSymbolHtml = symbolHtml;
// Add marker for the station
this.marker = L.marker([lat, lon], { icon: markerIcon })
.addTo(this.map)
.bindPopup(`<strong>${callsign}</strong><br/>Lat: ${lat.toFixed(6)}<br/>Lon: ${lon.toFixed(6)}`);
// Invalidate size after a short delay to ensure proper rendering
setTimeout(() => {
if (this.map) {
this.map.invalidateSize();
}
}, 250);
} catch (error) {
console.error("Error initializing InfoMap:", error);
}
},
createMarkerIcon(symbolHtml) {
if (symbolHtml) {
// Use the APRS symbol if provided
return L.divIcon({
html: symbolHtml,
className: 'aprs-info-marker',
iconSize: [32, 32],
iconAnchor: [16, 16]
});
} else {
// Default marker
return L.divIcon({
html: `<div style="
width: 24px;
height: 24px;
background-color: #3b82f6;
border: 3px solid white;
border-radius: 50%;
box-shadow: 0 2px 5px rgba(0,0,0,0.3);
"></div>`,
className: '',
iconSize: [24, 24],
iconAnchor: [12, 12]
});
}
},
destroyed() {
if (this.map) {
this.map.remove();
this.map = null;
}
}
};