11import { SlidingWindow , createEmptyCell , mergeIntoCell } from './sliding-window.js' ;
2+ import { StationSlidingWindow } from './station-window.js' ;
23import { indexPoint , shouldMaintainResolution } from '../compute/h3-indexer.js' ;
34import { haversineDistance , bearing , bearingToSector } from '../compute/distance.js' ;
45import { computeConfidence } from '../compute/signal-analysis.js' ;
@@ -18,8 +19,8 @@ import type { CoverageEvent, StationMeta, TransportType } from '../types/events.
1819export class CoverageStore {
1920 /** windows[resolutionIdx][windowIdx] = SlidingWindow */
2021 private windows : SlidingWindow [ ] [ ] = [ ] ;
21- /** Per-station directional coverage: stationId -> windowName -> StationCoverage */
22- private stationCoverage = new Map < number , Map < string , StationCoverage > > ( ) ;
22+ /** Per-station directional coverage with sliding windows: one per window config */
23+ private stationWindows : StationSlidingWindow [ ] = [ ] ;
2324 /** Synced station metadata from main API */
2425 public stationMap = new Map < number , StationMeta > ( ) ;
2526 /** Rate limiter */
@@ -45,6 +46,11 @@ export class CoverageStore {
4546 }
4647 this . windows . push ( resWindows ) ;
4748 }
49+
50+ // Initialize station sliding windows (one per time window config)
51+ for ( const wc of WINDOW_CONFIGS ) {
52+ this . stationWindows . push ( new StationSlidingWindow ( wc ) ) ;
53+ }
4854 }
4955
5056 /**
@@ -175,66 +181,24 @@ export class CoverageStore {
175181
176182 private updateStationCoverage (
177183 event : CoverageEvent ,
178- stationLat : number ,
179- stationLon : number ,
184+ _stationLat : number ,
185+ _stationLon : number ,
180186 distance : number ,
181187 brng : number ,
182188 hasValidTarget : boolean ,
183189 ) : void {
184- let stationWindows = this . stationCoverage . get ( event . stationId ) ;
185- if ( ! stationWindows ) {
186- stationWindows = new Map ( ) ;
187- this . stationCoverage . set ( event . stationId , stationWindows ) ;
188- }
189-
190- for ( const wc of WINDOW_CONFIGS ) {
191- let cov = stationWindows . get ( wc . name ) ;
192- if ( ! cov ) {
193- cov = {
194- stationId : event . stationId ,
195- bearingSectors : new Float64Array ( 36 ) ,
196- sectorMessageCounts : new Uint32Array ( 36 ) ,
197- sectorAvgLevels : new Float64Array ( 36 ) ,
198- totalMessages : 0 ,
199- messagesWithPosition : 0 ,
200- maxDistance : 0 ,
201- avgLevel : 0 ,
202- errorRate : 0 ,
203- confidence : 0 ,
204- lastUpdated : Date . now ( ) ,
205- } ;
206- stationWindows . set ( wc . name , cov ) ;
207- }
208-
209- cov . totalMessages ++ ;
210- cov . lastUpdated = Date . now ( ) ;
211-
212- if ( hasValidTarget ) {
213- cov . messagesWithPosition ++ ;
214- if ( distance > cov . maxDistance ) cov . maxDistance = distance ;
215-
216- const sector = bearingToSector ( brng ) ;
217- if ( distance > cov . bearingSectors [ sector ] ) {
218- cov . bearingSectors [ sector ] = distance ;
219- }
220- cov . sectorMessageCounts [ sector ] ++ ;
221-
222- if ( event . signalLevel !== null ) {
223- // Running average
224- const prevAvg = cov . sectorAvgLevels [ sector ] ;
225- const count = cov . sectorMessageCounts [ sector ] ;
226- cov . sectorAvgLevels [ sector ] = prevAvg + ( event . signalLevel - prevAvg ) / count ;
227- }
228- }
229-
230- if ( event . signalLevel !== null ) {
231- const prevAvg = cov . avgLevel ;
232- cov . avgLevel = prevAvg + ( event . signalLevel - prevAvg ) / cov . totalMessages ;
233- }
234-
235- if ( event . errorCount !== null && event . errorCount > 0 ) {
236- cov . errorRate = ( cov . errorRate * ( cov . totalMessages - 1 ) + 1 ) / cov . totalMessages ;
237- }
190+ const sector = hasValidTarget ? bearingToSector ( brng ) : - 1 ;
191+ const hasError = event . errorCount !== null && event . errorCount > 0 ;
192+
193+ for ( const sw of this . stationWindows ) {
194+ sw . record (
195+ event . stationId ,
196+ sector ,
197+ distance ,
198+ event . signalLevel ,
199+ hasValidTarget ,
200+ hasError ,
201+ ) ;
238202 }
239203 }
240204
@@ -288,19 +252,22 @@ export class CoverageStore {
288252 return results ;
289253 }
290254
291- /** Get station coverage data for a specific station and window */
255+ /** Get station coverage data for a specific station and window (sliding window scoped) */
292256 getStationCoverage ( stationId : number , windowName : string ) : StationCoverage | null {
293- return this . stationCoverage . get ( stationId ) ?. get ( windowName ) ?? null ;
257+ const wi = WINDOW_CONFIGS . findIndex ( c => c . name === windowName ) ;
258+ if ( wi === - 1 ) return null ;
259+ return this . stationWindows [ wi ] . getStationCoverage ( stationId ) ;
294260 }
295261
296262 /** Get all stations with coverage data for a given window */
297263 getAllStationCoverage ( windowName : string ) : StationCoverage [ ] {
264+ const wi = WINDOW_CONFIGS . findIndex ( c => c . name === windowName ) ;
265+ if ( wi === - 1 ) return [ ] ;
266+ const sw = this . stationWindows [ wi ] ;
298267 const results : StationCoverage [ ] = [ ] ;
299- for ( const [ , windows ] of this . stationCoverage ) {
300- const cov = windows . get ( windowName ) ;
301- if ( cov && cov . totalMessages > 0 ) {
302- results . push ( cov ) ;
303- }
268+ for ( const id of sw . activeStationIds ( ) ) {
269+ const cov = sw . getStationCoverage ( id ) ;
270+ if ( cov && cov . totalMessages > 0 ) results . push ( cov ) ;
304271 }
305272 return results ;
306273 }
@@ -360,7 +327,7 @@ export class CoverageStore {
360327 eventsRejected : this . eventsRejected ,
361328 eventsWithPosition : this . eventsWithPosition ,
362329 eventsWithoutPosition : this . eventsWithoutPosition ,
363- stationsTracked : this . stationCoverage . size ,
330+ stationsTracked : this . stationWindows . length > 0 ? this . stationWindows [ 2 ] . activeStationIds ( ) . size : 0 ,
364331 activeCells,
365332 } ;
366333 }
@@ -371,6 +338,7 @@ export class CoverageStore {
371338 window . destroy ( ) ;
372339 }
373340 }
341+ for ( const sw of this . stationWindows ) sw . destroy ( ) ;
374342 this . rateLimiter . destroy ( ) ;
375343 }
376344}
0 commit comments