import { ID , Line , LinePointsEvent , ObjectWithId , Point } from "facilmap-types" ; import { FeatureGroup , latLng , LayerOptions , Map , PolylineOptions } from "leaflet" ; import { HighlightableLayerOptions , HighlightablePolyline } from "leaflet-highlightable-layers" ; import { BasicTrackPoints , disconnectSegmentsOutsideViewport , tooltipOptions , trackPointsToLatLngArray } from "../utils/leaflet" ; import { numberKeys , quoteHtml } from "facilmap-utils" ; import { addClickListener , ClickListenerHandle } from "../click-listener/click-listener" ; import Client from "facilmap-client" ; interface LinesLayerOptions extends LayerOptions { } export default class LinesLayer extends FeatureGroup { options ! : LayerOptions ; client : Client < any > ; linesById : Record < string , InstanceType < typeof HighlightablePolyline > > = { } ; highlightedLinesIds = new Set < ID > ( ) ; hiddenLinesIds = new Set < ID > ( ) ; constructor ( client , options ? ) { super ( [ ] , options ) ; this . client = client ; } onAdd ( map ) { super . onAdd ( map ) ; this . client . on ( "line" , this . handleLine ) ; this . client . on ( "linePoints" , this . handleLinePoints ) ; this . client . on ( "deleteLine" , this . handleDeleteLine ) ; map . on ( "moveend" , this . handleMoveEnd ) ; map . on ( "fmFilter" , this . handleFilter ) ; if ( map . _loaded ) this . handleMoveEnd ( ) ; return this ; } onRemove ( map ) { super . onRemove ( map ) ; this . client . removeListener ( "line" , this . handleLine ) ; this . client . removeListener ( "linePoints" , this . handleLinePoints ) ; this . client . removeListener ( "deleteLine" , this . handleDeleteLine ) ; map . off ( "moveend" , this . handleMoveEnd ) ; map . off ( "fmFilter" , this . handleFilter ) ; return this ; } shouldShowLine ( line ) { return ! this . hiddenLinesIds . has ( line . id ) && this . _map . fmFilterFunc ( line , this . client . types [ line . typeId ] ) ; } handleLine = ( line ) : void => { if ( this . shouldShowLine ( line ) ) this . _addLine ( this . client . lines [ line . id ] ) ; } ; handleLinePoints = ( event ) : void => { const line = this . client . lines [ event . id ] ; if ( line && this . shouldShowLine ( line ) ) this . _addLine ( this . client . lines [ line . id ] ) ; } ; handleDeleteLine = ( data ) : void => { this . _deleteLine ( data ) ; } ; handleMoveEnd = ( ) : void => { Promise . resolve ( ) . then ( ( ) => { for ( const i of numberKeys ( this . client . lines ) ) { if ( this . shouldShowLine ( this . client . lines [ i ] ) ) this . _addLine ( this . client . lines [ i ] ) ; } } ) ; } ; handleFilter = ( ) : void => { for ( const i of numberKeys ( this . client . lines ) ) { const show = this . shouldShowLine ( this . client . lines [ i ] ) ; if ( this . linesById [ i ] && ! show ) this . _deleteLine ( this . client . lines [ i ] ) ; else if ( ! this . linesById [ i ] && show ) this . _addLine ( this . client . lines [ i ] ) ; } } ; highlightLine ( id ) { this . highlightedLinesIds . add ( id ) ; if ( this . client . lines [ id ] ) this . handleLine ( this . client . lines [ id ] ) ; } unhighlightLine ( id ) { this . highlightedLinesIds . delete ( id ) ; if ( this . client . lines [ id ] ) this . handleLine ( this . client . lines [ id ] ) ; } setHighlightedLines ( ids ) { for ( const id of this . highlightedLinesIds ) { if ( ! ids . has ( id ) ) this . unhighlightLine ( id ) ; } for ( const id of ids ) { if ( ! this . highlightedLinesIds . has ( id ) ) this . highlightLine ( id ) ; } } hideLine ( id ) { this . hiddenLinesIds . add ( id ) ; if ( this . client . lines [ id ] ) this . _deleteLine ( this . client . lines [ id ] ) ; } unhideLine ( id ) { this . hiddenLinesIds . delete ( id ) ; if ( this . client . lines [ id ] ) this . handleLine ( this . client . lines [ id ] ) ; } endDrawLine ( save = false ) { if ( ! this . _endDrawLine ) throw new Error ( "No drawing in process." ) ; else this . _endDrawLine ( save ) ; } _endDrawLine ? : ( save : boolean ) => void ; drawLine ( lineTemplate ) { return new Promise < Point [ ] | undefined > ( ( resolve ) => { const line = { ... lineTemplate , routePoints : [ ] , trackPoints : [ ] } ; line . trackPoints = line . routePoints ; const routePoints = [ ] ; let handler = undefined ; const addPoint = ( pos ) => { routePoints . push ( pos ) ; line . routePoints . push ( pos ) ; if ( line . routePoints . length == 1 ) line . routePoints . push ( pos ) ; this . _addLine ( line ) ; handler = addClickListener ( this . _map , handleClick , handleMouseMove ) ; } ; const handleClick = ( pos ) => { handler = undefined ; if ( routePoints . length > 0 && pos . lon == routePoints [ routePoints . length - 1 ] . lon && pos . lat == routePoints [ routePoints . length - 1 ] . lat ) finishLine ( true ) ; else addPoint ( pos ) ; } ; const handleMouseMove = ( pos ) => { if ( line . routePoints ! . length > 0 ) { line . routePoints ! [ line . routePoints ! . length - 1 ] = pos ; this . _addLine ( line ) ; } } ; const finishLine = async ( save ) => { if ( handler ) handler . cancel ( ) ; this . _deleteLine ( line ) ; delete this . _endDrawLine ; if ( save && routePoints . length >= 2 ) resolve ( routePoints ) ; else resolve ( undefined ) ; } ; handler = addClickListener ( this . _map , handleClick , handleMouseMove ) ; this . _endDrawLine = finishLine ; } ) ; } _addLine ( line ) { const trackPoints = line . mode ? trackPointsToLatLngArray ( line . trackPoints ) : line . routePoints . map ( ( p ) => latLng ( p . lat , p . lon ) ) ; const splitLatLngs = line . mode ? disconnectSegmentsOutsideViewport ( trackPoints , this . _map . getBounds ( ) ) : [ trackPoints ] ; if ( splitLatLngs . length == 0 ) { this . _deleteLine ( line ) ; return ; } if ( ! this . linesById [ line . id ] ) { this . linesById [ line . id ] = new HighlightablePolyline ( [ ] ) ; if ( line . id != null ) { this . linesById [ line . id ] . bindTooltip ( "" , { ... tooltipOptions , sticky : true , offset : [ 20 , 0 ] } ) . on ( "tooltipopen" , ( ) => { this . linesById [ line . id ] . setTooltipContent ( quoteHtml ( this . client . lines [ line . id ] . name ) ) ; } ) ; } } const style = { color : '#' + line . colour , weight : line . width , raised : false , opacity : 0.35 } as any ; if ( line . id == null || this . highlightedLinesIds . has ( line . id ) ) { Object . assign ( style , { raised : true , opacity : 1 } ) ; } ( this . linesById [ line . id ] as any ) . line = line ; this . linesById [ line . id ] . setLatLngs ( splitLatLngs ) . setStyle ( style ) ; if ( ! this . hasLayer ( this . linesById [ line . id ] ) ) this . addLayer ( this . linesById [ line . id ] ) ; } _deleteLine ( line ) { if ( ! this . linesById [ line . id ] ) return ; this . removeLayer ( this . linesById [ line . id ] ) ; delete this . linesById [ line . id ] ; } }