feat: zoom on click, google maps click fix, and activity hysteresis

This commit is contained in:
Nicole 2025-11-19 21:31:42 +01:00
parent 66906f2eed
commit 3d7c74b325
3 changed files with 57 additions and 11 deletions

View file

@ -46,8 +46,7 @@ export const MARKER_SIZES = {
export const ACTIVITY_THRESHOLDS = {
still: 1,
walking: 7,
cycling: 25,
vehicle: 25
vehicle: 7
};
/**

View file

@ -11,6 +11,8 @@ export class EntityDataFetcher {
this._entities = [];
this._positionHistory = new Map(); // entityId → PositionEntry[]
this._lastPredictedActivity = new Map(); // entityId → string (last known predicted activity)
this._candidateActivity = new Map(); // entityId → { activity: string, timestamp: number }
this._activityStabilityMs = 3000; // 3 second hysteresis
}
/**
@ -138,12 +140,23 @@ export class EntityDataFetcher {
// We use optional chaining and strict equality to ensure we only enter this block
// if the user explicitly selected 'speed_predicted'
if (config?.activity_source === 'speed_predicted') {
if (data.speed) {
// Use predicted activity and cache it for sticky behavior
const predictedActivity = data.predicted_activity || 'unknown';
this._lastPredictedActivity.set(entityId, predictedActivity);
this._log(`Activity for ${entityId}: predicted(${predictedActivity}) - speed available`);
return predictedActivity;
if (data.speed && data.speed.speed_kmh !== null && data.speed.speed_kmh !== undefined) {
// Use stable predicted activity with hysteresis
const activity = this._getStablePredictedActivity(entityId, data.speed.speed_kmh);
if (activity) {
this._log(`Activity for ${entityId}: predicted(${activity}) - speed available`);
return activity;
} else {
// Activity is stabilizing, use last predicted activity if available
const lastActivity = this._lastPredictedActivity.get(entityId);
if (lastActivity) {
this._log(`Activity for ${entityId}: stable(${lastActivity}) - stabilizing`);
return lastActivity;
}
// No last activity, use unknown
this._log(`Activity for ${entityId}: unknown - stabilizing, no last activity`);
return 'unknown';
}
} else {
// Speed is null, use sticky last predicted activity if available
const lastActivity = this._lastPredictedActivity.get(entityId);
@ -323,13 +336,43 @@ export class EntityDataFetcher {
return 'still';
} else if (speedKmh < ACTIVITY_THRESHOLDS.walking) {
return 'walking';
} else if (speedKmh < ACTIVITY_THRESHOLDS.cycling) {
return 'on_bicycle';
} else {
return 'in_vehicle';
}
}
/**
* Gets stable predicted activity with 3-second hysteresis
* @param {string} entityId - Entity identifier
* @param {number} speedKmh - Speed in km/h
* @returns {string|null} Activity if stable, null if stabilizing
*/
_getStablePredictedActivity(entityId, speedKmh) {
const currentActivity = this.predictActivity(speedKmh);
const candidate = this._candidateActivity.get(entityId);
const now = Date.now();
if (!candidate || candidate.activity !== currentActivity) {
// New candidate activity, start tracking
this._candidateActivity.set(entityId, { activity: currentActivity, timestamp: now });
this._log(`Activity candidate for ${entityId}: ${currentActivity} (new)`);
return this._lastPredictedActivity.get(entityId) || null;
}
// Same candidate, check if we've passed stability period
const elapsed = now - candidate.timestamp;
if (elapsed > this._activityStabilityMs) {
// Activity is stable, update last predicted activity
this._lastPredictedActivity.set(entityId, currentActivity);
this._log(`Activity stable for ${entityId}: ${currentActivity} (elapsed: ${elapsed}ms)`);
return currentActivity;
}
// Still stabilizing, return last stable activity
this._log(`Activity stabilizing for ${entityId}: ${currentActivity} (elapsed: ${elapsed}ms)`);
return this._lastPredictedActivity.get(entityId) || null;
}
/**
* Updates position history for an entity (ring buffer of max 5 entries)
* @param {string} entityId - Entity identifier

View file

@ -582,6 +582,7 @@ function updateMarkerOSM(entityId, data, lat, lon, personState, activityState, p
currentPopup.closePopup();
}
currentPopup = marker;
map.setView(marker.getLatLng(), 17, { animate: true });
});
markers[entityId] = marker;
@ -634,13 +635,16 @@ function updateMarkerGoogle(entityId, data, lat, lon, personState, activityState
div.title = this.title;
// Add click event for info window
div.addEventListener('click', () => {
div.addEventListener('click', (e) => {
e.stopPropagation();
// Close currently open popup
if (currentPopup && currentPopup !== this.infoWindow) {
currentPopup.close();
}
if (this.infoWindow) {
map.setCenter(this.position);
map.setZoom(17);
this.infoWindow.open(this.getMap());
currentPopup = this.infoWindow;
}