import {LayerGroup, LayerOptions, Marker, Polyline} from 'leaflet'; import { DEFAULT_EVENTS } from '../events/default-events'; import { PlayBackData, PlayBackElement } from './PlaybackData'; import {EventPlayerBase} from '../EventPlayer'; import { TrackableMarker } from '../TrackableMarker'; export class PlayBack extends EventPlayerBase { private tracks:LayerGroup = new LayerGroup(); private data:PlayBackData = null; private timeMs:number = 0; private tracking:boolean = true; private forward:boolean = true; constructor(data:PlayBackData,options?:LayerOptions) { super(options); this.data = data.sort((a,b) => a.time - b.time); this.timeMs = this.MinimumTime; this.tracks.addTo(this); this.registerEvents(DEFAULT_EVENTS); } createMarker(marker:TrackableMarker,name:string) : TrackableMarker { const added = super.createMarker(marker,name); if(added) { added.TracksLayer = this.tracks; } return added; } removeMarker(id:string) { this.markers.get(id)?.removeTracks(); super.removeMarker(id); } set Time(ms:number) { if (ms === this.timeMs) return; this.forward = ms > this.timeMs; const interval = Math.abs(this.timeMs - ms); this.timeMs = ms; this.execEvents(this.forward,interval); } get Time() : number { return this.timeMs; } get MinimumTime() : number { return this.data[0].time; } get MaximumTime() : number { return this.data[this.data.length -1].time; } get Track() : boolean { return this.tracking; } set Track(t:boolean) { this.tracking = t; if(this.tracking) { this.tracks.addTo(this); } else { this.removeLayer(this.tracks); } } get Data() : PlayBackData { return this.data; } private execEvents(forward:boolean,interval:number) { const events = this.findClosestEventsToTime(forward,interval); try { if(forward) { events.forEach(e => { if(!e) return; const event = this.events.get(e.event.name); if(!event) { console.error('Unknown event: ' + e.event.name); return; } event.do({playback:this},e.event.args); }); } else { //using reduceright for backwards foreach events.reduceRight((_,e) => { if(!e) return; const event = this.events.get(e.event.name); if(!event) { console.error('Unknown event: ' + e.event.name); return; } event.undo({playback:this},e.event.args); return null; },null) } } catch(e:any) { console.error(e.toString()) } } private findClosestEventsToTime(forward:boolean,interval:number) : PlayBackElement[] { let range: [number,number] = [0,0] if (forward) range = [ this.findClosestEventIndex(this.timeMs - interval,true), this.findClosestEventIndex(this.timeMs,false) ]; else range = [ this.findClosestEventIndex(this.timeMs,true), this.findClosestEventIndex(this.timeMs + interval,false) ]; const data = this.data.slice( range[0], range[1] + 1 ); return data; } private findClosestEventIndex(value:number,lowestIndex:boolean) { if(value <= this.data[0].time || (value === this.MinimumTime && lowestIndex)) return 0; if (value >= this.data[this.data.length - 1].time || (value === this.MaximumTime && !lowestIndex)) return this.data.length -1; let low = 0; let hi = this.data.length -1; while(low <= hi) { let mid = Math.floor((hi+low)/2); if(value < this.data[mid].time) hi = mid - 1; else if(value > this.data[mid].time) low = mid + 1; else return mid; } let i = lowestIndex ? low : hi; const increment = lowestIndex ? -1 : 1; while(this.data[i].time === this.data[i+increment].time) { i += increment; } return i; } }