fix(rcal): map shows only events from visible calendar period

Previously the map rendered ALL events regardless of which time period
the calendar was displaying. Now markers, transit lines, and map bounds
are filtered to the visible date range (day/week/month/season/year).
The map auto-fits to the bounds of visible located events when zoom
coupling is active.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Jeff Emmett 2026-03-09 21:54:05 -07:00
parent e7c285c752
commit 5053d69ade
1 changed files with 66 additions and 9 deletions

View File

@ -310,16 +310,65 @@ class FolkCalendarView extends HTMLElement {
return S_TO_ZOOM[this.getEffectiveSpatialIndex()];
}
/** Returns [start, end] epoch-ms bounds for the currently visible calendar period. */
private getVisibleDateRange(): [number, number] {
const d = this.currentDate;
const y = d.getFullYear(), m = d.getMonth(), dt = d.getDate();
switch (this.viewMode) {
case "day":
return [new Date(y, m, dt).getTime(), new Date(y, m, dt + 1).getTime()];
case "week": {
const ws = new Date(y, m, dt - d.getDay());
return [ws.getTime(), new Date(ws.getFullYear(), ws.getMonth(), ws.getDate() + 7).getTime()];
}
case "month":
return [new Date(y, m, 1).getTime(), new Date(y, m + 1, 1).getTime()];
case "season": {
const q = Math.floor(m / 3);
return [new Date(y, q * 3, 1).getTime(), new Date(y, q * 3 + 3, 1).getTime()];
}
case "year":
return [new Date(y, 0, 1).getTime(), new Date(y + 1, 0, 1).getTime()];
case "multi-year": {
const sy = y - 4;
return [new Date(sy, 0, 1).getTime(), new Date(sy + 9, 0, 1).getTime()];
}
default:
return [new Date(y, m, 1).getTime(), new Date(y, m + 1, 1).getTime()];
}
}
/** Events with coordinates that fall within the visible calendar period. */
private getVisibleLocatedEvents(): any[] {
const [start, end] = this.getVisibleDateRange();
return this.events.filter(e => {
if (e.latitude == null || e.longitude == null) return false;
if (this.filteredSources.has(e.source_name)) return false;
const t = new Date(e.start_time).getTime();
return t >= start && t < end;
});
}
private syncMapToSpatial() {
if (!this.leafletMap) return;
const zoom = this.getEffectiveLeafletZoom();
const center = this.computeMapCenter();
this.leafletMap.flyTo(center, zoom, { duration: 0.8 });
const L = (window as any).L;
if (!L) return;
const located = this.getVisibleLocatedEvents();
if (located.length === 0) {
// No events in view — fly to coupled zoom at default center
this.leafletMap.flyTo([52.52, 13.405], this.getEffectiveLeafletZoom(), { duration: 0.8 });
return;
}
if (located.length === 1) {
this.leafletMap.flyTo([located[0].latitude, located[0].longitude], this.getEffectiveLeafletZoom(), { duration: 0.8 });
return;
}
const bounds = L.latLngBounds(located.map((e: any) => [e.latitude, e.longitude]));
this.leafletMap.flyToBounds(bounds, { padding: [40, 40], maxZoom: 16, duration: 0.8 });
}
private computeMapCenter(): [number, number] {
const located = this.events.filter(e =>
e.latitude != null && e.longitude != null && !this.filteredSources.has(e.source_name));
const located = this.getVisibleLocatedEvents();
if (located.length === 0) return [52.52, 13.405];
let sumLat = 0, sumLng = 0;
for (const e of located) { sumLat += e.latitude; sumLng += e.longitude; }
@ -1868,8 +1917,7 @@ class FolkCalendarView extends HTMLElement {
if (!L || !this.mapMarkerLayer) return;
this.mapMarkerLayer.clearLayers();
const located = this.events.filter(e =>
e.latitude != null && e.longitude != null && !this.filteredSources.has(e.source_name));
const located = this.getVisibleLocatedEvents();
for (const ev of located) {
const marker = L.circleMarker([ev.latitude, ev.longitude], {
@ -1883,6 +1931,16 @@ class FolkCalendarView extends HTMLElement {
</div>`);
this.mapMarkerLayer.addLayer(marker);
}
// Auto-fit map to visible event bounds
if (this.zoomCoupled && located.length > 0) {
if (located.length === 1) {
this.leafletMap?.flyTo([located[0].latitude, located[0].longitude], this.getEffectiveLeafletZoom(), { duration: 0.8 });
} else {
const bounds = L.latLngBounds(located.map((e: any) => [e.latitude, e.longitude]));
this.leafletMap?.flyToBounds(bounds, { padding: [40, 40], maxZoom: 16, duration: 0.8 });
}
}
}
private updateTransitLines() {
@ -1890,8 +1948,7 @@ class FolkCalendarView extends HTMLElement {
if (!L || !this.transitLineLayer) return;
this.transitLineLayer.clearLayers();
const sorted = this.events
.filter(e => e.latitude != null && e.longitude != null && !this.filteredSources.has(e.source_name))
const sorted = this.getVisibleLocatedEvents()
.sort((a, b) => new Date(a.start_time).getTime() - new Date(b.start_time).getTime());
if (sorted.length < 2) return;