fix(map): tighten edge cases in heatmap, info map, and trail color
- Heatmap: filter points with invalid coords or out-of-range intensity so malformed rows can't distort the layer. - InfoMap: validate coords for range (not just NaN) and use an epsilon when comparing positions, so float noise doesn't cause needless re-renders or accept bogus data. - TrailManager: getTrailCenter returns null for empty input instead of the ambiguous (0, 0) null-island; callers guard accordingly.
This commit is contained in:
parent
52418a7b41
commit
e90226dc21
3 changed files with 51 additions and 18 deletions
|
|
@ -304,12 +304,13 @@ export class TrailManager {
|
|||
return unwrapped;
|
||||
}
|
||||
|
||||
// Get the average position of a trail
|
||||
// Get the average position of a trail. Returns null for empty arrays so
|
||||
// callers can't accidentally treat null island (0, 0) as a real center.
|
||||
private getTrailCenter(positions: PositionHistory[]): {
|
||||
lat: number;
|
||||
lng: number;
|
||||
} {
|
||||
if (positions.length === 0) return { lat: 0, lng: 0 };
|
||||
} | null {
|
||||
if (positions.length === 0) return null;
|
||||
|
||||
const sum = positions.reduce(
|
||||
(acc, pos) => ({
|
||||
|
|
@ -341,6 +342,8 @@ export class TrailManager {
|
|||
return;
|
||||
|
||||
const otherCenter = this.getTrailCenter(trailState.positions);
|
||||
if (!otherCenter) return;
|
||||
|
||||
const distance = this.calculateDistance(
|
||||
center.lat,
|
||||
center.lng,
|
||||
|
|
@ -377,8 +380,8 @@ export class TrailManager {
|
|||
}
|
||||
|
||||
// All 15 colors in use — pick one not used by nearby trails
|
||||
if (positions.length > 0) {
|
||||
const center = this.getTrailCenter(positions);
|
||||
const center = this.getTrailCenter(positions);
|
||||
if (center) {
|
||||
const nearbyColors = this.getNearbyTrailColors(baseCallsign, center);
|
||||
for (const color of this.colorPalette) {
|
||||
if (!nearbyColors.has(color)) {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,23 @@
|
|||
|
||||
declare const L: typeof import("leaflet");
|
||||
|
||||
// ~1.1 m at the equator — enough to ignore float noise but still notice any
|
||||
// real position change from the server.
|
||||
const POSITION_EPSILON = 0.00001;
|
||||
|
||||
function isValidLatLng(lat: number, lng: number): boolean {
|
||||
return (
|
||||
!isNaN(lat) &&
|
||||
!isNaN(lng) &&
|
||||
isFinite(lat) &&
|
||||
isFinite(lng) &&
|
||||
lat >= -90 &&
|
||||
lat <= 90 &&
|
||||
lng >= -180 &&
|
||||
lng <= 180
|
||||
);
|
||||
}
|
||||
|
||||
interface InfoMapContext {
|
||||
el: HTMLElement;
|
||||
map: import("leaflet").Map | null;
|
||||
|
|
@ -29,7 +46,7 @@ export const InfoMap = {
|
|||
const symbolHtml = this.el.dataset.symbolHtml || null;
|
||||
const callsign = this.el.dataset.callsign || "unknown";
|
||||
|
||||
if (isNaN(lat) || isNaN(lon)) {
|
||||
if (!isValidLatLng(lat, lon)) {
|
||||
console.warn(
|
||||
`InfoMap: Invalid coordinates lat=${lat}, lon=${lon} for ${callsign} during update`,
|
||||
);
|
||||
|
|
@ -47,7 +64,10 @@ export const InfoMap = {
|
|||
|
||||
if (this.marker) {
|
||||
const currentPos = this.marker.getLatLng();
|
||||
if (currentPos.lat !== lat || currentPos.lng !== lon) {
|
||||
if (
|
||||
Math.abs(currentPos.lat - lat) > POSITION_EPSILON ||
|
||||
Math.abs(currentPos.lng - lon) > POSITION_EPSILON
|
||||
) {
|
||||
this.marker.setLatLng([lat, lon]);
|
||||
|
||||
this.marker.setPopupContent(
|
||||
|
|
@ -97,7 +117,7 @@ function initializeMap(this: InfoMapContext) {
|
|||
const callsign = this.el.dataset.callsign || "unknown";
|
||||
const symbolHtml = this.el.dataset.symbolHtml || null;
|
||||
|
||||
if (isNaN(lat) || isNaN(lon)) {
|
||||
if (!isValidLatLng(lat, lon)) {
|
||||
console.warn(
|
||||
`InfoMap: Invalid coordinates lat=${lat}, lon=${lon} for ${callsign}`,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1575,16 +1575,26 @@ let MapAPRSMap = {
|
|||
}
|
||||
}
|
||||
|
||||
// Convert heat points to format expected by Leaflet.heat
|
||||
// Intensity is pre-normalized to 0-1 range by the server
|
||||
const heatData = data.heat_points.map(
|
||||
(point) =>
|
||||
[point.lat, point.lng, point.intensity] as [
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
],
|
||||
);
|
||||
// Convert heat points to format expected by Leaflet.heat.
|
||||
// Server pre-normalizes intensity to [0, 1] but validate defensively
|
||||
// so malformed rows can't distort the whole heat layer.
|
||||
const heatData = data.heat_points
|
||||
.filter(
|
||||
(point) =>
|
||||
isValidCoordinate(point.lat, point.lng) &&
|
||||
typeof point.intensity === "number" &&
|
||||
isFinite(point.intensity) &&
|
||||
point.intensity >= 0 &&
|
||||
point.intensity <= 1,
|
||||
)
|
||||
.map(
|
||||
(point) =>
|
||||
[point.lat, point.lng, point.intensity] as [
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
],
|
||||
);
|
||||
|
||||
// Update heat layer data
|
||||
self.heatLayer.setLatLngs(heatData);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue