import { GenericGeoJSONFeatureCollection } from "@yaga/generic-geojson" ; import { IntervalTree } from "diesal" ; ignore import L = require ( "leaflet" ) ; export type TimedGeoJSON = GenericGeoJSONFeatureCollection < GeoJSON . Geometry , { start : string | number ; end : string | number ; startExclusive ? : string | boolean ; endExclusive ? : string | boolean ; } > ; export interface TimelineOptions extends L . GeoJSONOptions { /**
     * If true (default), the layer will update as soon as `setTime` is called.
     *
     * If `false`, you must call `updateDisplayedLayers()` to update the display to
     * the current time. This is useful if you have complex data and performance
     * becomes a concern.
     */ drawOnSetTime ? : boolean ; /**
     * Called for each feature, and should return either a time range for the
     * feature or `false`, indicating that it should not be included in the
     * timeline.
     *
     * If not provided, it assumes that the start/end are already part of the
     * feature object.
     */ getInterval ? ( feature ) : TimeBounds | false ; start ? : number ; end ? : number ; } export interface TimeBounds { start : number ; end : number ; /**
     * Consider the `start` bound to exclusive, i.e., only matching `time > start` (instead of `time >= start`)
     */ startExclusive ? : boolean ; /**
     * Consider the `end` bound to exclusive, i.e., only matching `time < end` (instead of `time <= end`)
     */ endExclusive ? : boolean ; } declare module "leaflet" { export class Timeline extends L . GeoJSON { start : number ; end : number ; time : number ; times : number [ ] ; ranges : IntervalTree < GeoJSON . Feature > ; options : Required < TimelineOptions > ; ignore initialize ( geojson , options ? ) ; ignore _getInterval ( feature ) ; ignore _process ( geojson ) ; updateDisplayedLayers ( ) ; getLayers ( ) ; setTime ( time ) ; } let timeline ; } L . Timeline = L . GeoJSON . extend ( { times : null , ranges : null , initialize ( this , geojson , options = { } ) { this . times = [ ] ; this . ranges = new IntervalTree ( ) ; const defaultOptions = { drawOnSetTime : true } ; L . GeoJSON . prototype . initialize . call ( this , null , options ) ; L . Util . setOptions ( this , defaultOptions ) ; L . Util . setOptions ( this , options ) ; if ( this . options . getInterval ) { this . _getInterval = ( feature ) => this . options . getInterval ( feature ) ; } if ( geojson ) { this . _process ( geojson ) ; } } , _getInterval ( this , feature ) { if ( feature . properties && "start" in feature . properties && "end" in feature . properties ) { const { startExclusive , endExclusive } = feature . properties ; return { start : new Date ( feature . properties . start ) . getTime ( ) , end : new Date ( feature . properties . end ) . getTime ( ) , startExclusive : startExclusive === true || startExclusive === "true" , endExclusive : endExclusive === true || endExclusive === "true" , } ; } return false ; } , param data _process ( this , data ) { data . features . forEach ( feature => { const interval = this . _getInterval ( feature ) ; if ( ! interval ) { return ; } this . ranges . insert ( interval . start + ( interval . startExclusive ? 1 : 0 ) , interval . end - ( interval . endExclusive ? 1 : 0 ) , feature ) ; this . times . push ( interval . start ) ; this . times . push ( interval . end ) ; } ) ; if ( this . times . length === 0 ) { this . start = this . options . start ?? Infinity ; this . end = this . options . end ?? - Infinity ; this . time = this . start ; return ; } this . times . sort ( ( a , b ) => a - b ) ; this . times = this . times . reduce ( ( newList , x , i ) => { if ( i === 0 ) { return newList ; } const lastTime = newList [ newList . length - 1 ] ; if ( lastTime !== x ) { newList . push ( x ) ; } return newList ; } , [ this . times [ 0 ] ] ) ; this . start = this . options . start ?? this . times [ 0 ] ; this . end = this . options . end ?? this . times [ this . times . length - 1 ] ; this . time = this . start ; } , param time setTime ( this , time ) { this . time = typeof time === "number" ? time : new Date ( time ) . getTime ( ) ; if ( this . options . drawOnSetTime ) { this . updateDisplayedLayers ( ) ; } this . fire ( "change" ) ; } , /**
     * Update the layer to show only the features that are relevant at the current
     * time. Usually shouldn't need to be called manually, unless you set
     * `drawOnSetTime` to `false`.
     */ updateDisplayedLayers ( this ) { const features = this . ranges . lookup ( this . time ) ; const layers = this . getLayers ( ) as L . GeoJSON [ ] ; const layersToRemove = [ ] ; layers . forEach ( layer => { let found = false ; for ( let j = 0 ; j < features . length ; j ++ ) { if ( layer . feature === features [ j ] ) { found = true ; features . splice ( j , 1 ) ; break ; } } if ( ! found ) { layersToRemove . push ( layer ) ; } } ) ; layersToRemove . forEach ( layer => this . removeLayer ( layer ) ) ; features . forEach ( feature => this . addData ( feature ) ) ; } } ) ; L . timeline = ( geojson ? , options ? ) => new L . Timeline ( geojson , options ) ;