2021-01-23 11:38:26 +00:00
import { Colour , Shape , Symbol } from "facilmap-types" ;
2021-02-28 22:17:26 +00:00
import { makeTextColour , quoteHtml } from "facilmap-utils" ;
import L , { Icon } from "leaflet" ;
2021-03-07 21:56:30 +00:00
import { memoize } from "lodash" ;
2021-01-23 11:38:26 +00:00
2021-01-31 07:59:12 +00:00
const rawIconsContext = require . context ( "../../assets/icons" ) ;
2021-01-23 11:38:26 +00:00
const rawIcons : Record < string , Record < string , string > > = { } ;
for ( const key of rawIconsContext . keys ( ) as string [ ] ) {
const [ set , fname ] = key . split ( "/" ) . slice ( - 2 ) ;
if ( ! rawIcons [ set ] )
rawIcons [ set ] = { } ;
rawIcons [ set ] [ fname . replace ( /\.svg$/ , "" ) ] = rawIconsContext ( key ) ;
}
2021-03-13 23:54:10 +00:00
rawIcons [ "fontawesome" ] = { } ;
2021-03-21 22:38:48 +00:00
for ( const name of [ "arrow-left" , "arrow-right" , "biking" , "car-alt" , "chart-line" , "info-circle" , "slash" , "walking" ] ) {
2021-03-13 23:54:10 +00:00
rawIcons [ "fontawesome" ] [ name ] = require ( ` @fortawesome/fontawesome-free/svgs/solid/ ${ name } .svg ` ) ;
}
2021-03-07 21:56:30 +00:00
export const symbolList = Object . keys ( rawIcons ) . map ( ( key ) = > Object . keys ( rawIcons [ key ] ) ) . flat ( ) ;
2021-01-23 11:38:26 +00:00
2021-03-04 15:45:34 +00:00
export const RAINBOW_STOPS = ` <stop offset="0" stop-color="red"/><stop offset="33%" stop-color="#ff0"/><stop offset="50%" stop-color="#0f0"/><stop offset="67%" stop-color="cyan"/><stop offset="100%" stop-color="blue"/> ` ;
2021-01-23 11:38:26 +00:00
interface ShapeInfo {
svg : string ;
highlightSvg : string ;
height : number ;
width : number ;
baseX : number ;
baseY : number ;
}
const MARKER_SHAPES : Partial < Record < Shape , ShapeInfo > > = {
drop : {
2021-03-07 21:56:30 +00:00
svg : (
` <path id="drop-%ID%" style="stroke: %BORDER_COLOUR%; stroke-width: 2; stroke-linecap: round; fill: %COLOUR%; clip-path: url(#clip-%ID%)" d="M13 0C4.727 0 0 4.8 0 13.2 0 21.6 11.744 36 13 36c1.256 0 13-14.4 13-22.8S21.273 0 13 0z"/>"/> ` +
` <clipPath id="clip-%ID%"><use xlink:href="#drop-%ID%"/></clipPath> ` + // Don't increase the size by increasing the border: https://stackoverflow.com/a/32162431/242365
` <g transform="translate(4, 4) scale(0.75)">%SYMBOL%</g> `
) ,
highlightSvg : (
` <path id="drop-%ID%" style="stroke: %BORDER_COLOUR%; stroke-width: 6; stroke-linecap: round; fill: %COLOUR%; clip-path: url(#clip-%ID%)" d="M13 0C4.727 0 0 4.8 0 13.2 0 21.6 11.744 36 13 36c1.256 0 13-14.4 13-22.8S21.273 0 13 0z"/>"/> ` +
` <clipPath id="clip-%ID%"><use xlink:href="#drop-%ID%"/></clipPath> ` +
` <g transform="translate(4, 4) scale(0.75)">%SYMBOL%</g> `
) ,
height : 36 ,
2021-01-23 11:38:26 +00:00
width : 26 ,
baseX : 13 ,
2021-03-07 21:56:30 +00:00
baseY : 36
} ,
circle : {
svg : (
` <circle id="circle-%ID%" style="stroke: %BORDER_COLOUR%; stroke-width: 2; fill: %COLOUR%; clip-path: url(#clip-%ID%)" cx="18" cy="18" r="18" /> ` +
` <clipPath id="clip-%ID%"><use xlink:href="#circle-%ID%"/></clipPath> ` +
` <g transform="translate(6, 6)">%SYMBOL%</g> `
) ,
highlightSvg : (
` <circle id="circle-%ID%" style="stroke: %BORDER_COLOUR%; stroke-width: 6; fill: %COLOUR%; clip-path: url(#clip-%ID%)" cx="18" cy="18" r="18" /> ` +
` <clipPath id="clip-%ID%"><use xlink:href="#circle-%ID%"/></clipPath> ` +
` <g transform="translate(6, 6)">%SYMBOL%</g> `
) ,
height : 36 ,
width : 36 ,
baseX : 18 ,
baseY : 18
2021-01-23 11:38:26 +00:00
}
} ;
2021-03-07 21:56:30 +00:00
export const shapeList = Object . keys ( MARKER_SHAPES ) as Shape [ ] ;
2021-01-23 11:38:26 +00:00
const sizes : Record < string , number > = {
osmi : 580 ,
mdiconic : 1000 ,
2021-03-13 23:54:10 +00:00
glyphicons : 1410 ,
fontawesome : 640
2021-01-23 11:38:26 +00:00
} ;
2021-03-07 21:56:30 +00:00
export const getLetterOffset = memoize ( ( letter : string ) : { x : number , y : number } = > {
const ctx = document . createElement ( "canvas" ) . getContext ( "2d" ) ! ;
ctx . font = "25px sans-serif" ;
const dim = ctx . measureText ( letter ) ;
2021-01-23 11:38:26 +00:00
2021-03-07 21:56:30 +00:00
const letterWidth = dim . width ; // dim.actualBoundingBoxRight + dim.actualBoundingBoxLeft;
const letterHeight = dim . actualBoundingBoxAscent + dim . actualBoundingBoxDescent ;
2021-01-23 11:38:26 +00:00
2021-03-07 21:56:30 +00:00
return {
x : ( 25 - letterWidth ) / 2 ,
y : ( 25 - letterHeight ) / 2 + dim . actualBoundingBoxAscent
} ;
} ) ;
2021-01-23 11:38:26 +00:00
2021-01-31 07:59:12 +00:00
export function getSymbolCode ( colour : Colour , size : number , symbol ? : Symbol ) : string {
2021-03-07 21:56:30 +00:00
if ( symbol && symbolList . includes ( symbol ) ) {
const set = Object . keys ( rawIcons ) . filter ( ( i ) = > ( rawIcons [ i ] [ symbol ] != null ) ) [ 0 ] ;
if ( set == "osmi" ) {
return ` <g transform="scale( ${ size / sizes . osmi } )"> ${ rawIcons [ set ] [ symbol ] . replace ( /#000/g , colour ) } </g> ` ;
}
2021-03-13 23:54:10 +00:00
const widthMatch = rawIcons [ set ] [ symbol ] . match ( /^<svg [^>]* width="([0-9.]+)"/ ) ;
const heightMatch = rawIcons [ set ] [ symbol ] . match ( /^<svg [^>]* height="([0-9.]+)"/ ) ;
const viewBoxMatch = rawIcons [ set ] [ symbol ] . match ( /^<svg [^>]* viewBox="([ 0-9.]+)"/ ) ;
const width = Number ( widthMatch ? widthMatch [ 1 ] : viewBoxMatch ! [ 1 ] . split ( " " ) [ 2 ] ) ;
const height = Number ( heightMatch ? heightMatch [ 1 ] : viewBoxMatch ! [ 1 ] . split ( " " ) [ 3 ] ) ;
2021-03-07 21:56:30 +00:00
const scale = size / sizes [ set ] ;
const moveX = ( sizes [ set ] - width ) / 2 ;
const moveY = ( sizes [ set ] - height ) / 2 ;
const content = rawIcons [ set ] [ symbol ] . replace ( /^<svg [^>]*>/ , "" ) . replace ( /<\/svg>$/ , "" ) ;
return ` <g transform="scale( ${ scale } ) translate( ${ moveX } , ${ moveY } )" fill=" ${ colour } "> ${ content } </g> ` ;
} else if ( symbol && symbol . length == 1 ) {
try {
const offset = getLetterOffset ( symbol ) ;
return (
` <g transform="scale( ${ size / 25 } ) translate( ${ offset . x } , ${ offset . y } )"> ` +
` <text style="font-size: 25px; font-family: sans-serif; fill: # ${ colour } "> ${ quoteHtml ( symbol ) } </text> ` +
` </g> `
) ;
} catch ( e ) {
console . error ( "Error creating letter symbol." , e ) ;
}
}
return ` <circle style="fill: ${ colour } " cx=" ${ Math . floor ( size / 2 ) } " cy=" ${ Math . floor ( size / 2 ) } " r=" ${ Math . floor ( size / 6 ) } " /> ` ;
2021-01-23 11:38:26 +00:00
}
2021-03-07 21:56:30 +00:00
export function getSymbolUrl ( colour : Colour , height : number , symbol ? : Symbol ) : string {
2021-02-28 22:17:26 +00:00
const svg = ` <?xml version="1.0" encoding="UTF-8" standalone="no"?> ` +
2021-03-07 21:56:30 +00:00
` <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width=" ${ height } " height=" ${ height } " viewbox="0 0 24 24" version="1.1"> ` +
getSymbolCode ( '#' + colour , 24 , symbol ) +
2021-01-23 11:38:26 +00:00
` </svg> ` ;
return ` data:image/svg+xml, ${ encodeURIComponent ( svg ) } ` ;
}
2021-03-07 21:56:30 +00:00
export function getSymbolHtml ( colour : Colour , height : number | string , symbol ? : Symbol ) : string {
return ` <svg width=" ${ height } " height=" ${ height } " viewbox="0 0 24 24"> ` +
getSymbolCode ( colour , 24 , symbol ) +
2021-01-23 11:38:26 +00:00
` </svg> ` ;
}
2021-03-07 21:56:30 +00:00
let idCounter = 0 ;
export function getMarkerCode ( colour : Colour , height : number , symbol ? : Symbol , shape? : Shape , highlight = false ) : string {
2021-02-28 22:17:26 +00:00
const borderColour = makeTextColour ( colour , 0.3 ) ;
2021-03-11 17:01:40 +00:00
const id = ` ${ idCounter ++ } ` ;
2021-01-23 11:38:26 +00:00
2021-02-28 22:17:26 +00:00
const shapeObj = ( shape && MARKER_SHAPES [ shape ] ) || MARKER_SHAPES . drop ! ;
const shapeCode = ( highlight ? shapeObj.highlightSvg : shapeObj.svg )
2021-01-23 11:38:26 +00:00
. replace ( /%BORDER_COLOUR%/g , "#" + borderColour )
2021-03-11 17:01:40 +00:00
. replace ( /%COLOUR%/g , colour == "rainbow" ? ` url(#fm-rainbow- ${ id } ) ` : "#" + colour )
2021-03-07 21:56:30 +00:00
. replace ( /%SYMBOL%/g , getSymbolCode ( "#" + borderColour , 24 , symbol ) )
2021-03-11 17:01:40 +00:00
. replace ( /%ID%/g , id ) ;
2021-03-07 21:56:30 +00:00
const scale = height / shapeObj . height ;
return (
` <g transform="scale( ${ scale } )"> ` +
2021-03-11 17:01:40 +00:00
( colour == "rainbow" ? ` <defs><linearGradient id="fm-rainbow- ${ id } " x2="0" y2="100%"> ${ RAINBOW_STOPS } </linearGradient></defs> ` : '' ) +
2021-03-07 21:56:30 +00:00
shapeCode +
` </g> `
) ;
}
2021-01-23 11:38:26 +00:00
2021-03-07 21:56:30 +00:00
export function getMarkerUrl ( colour : string , height : number , symbol ? : Symbol , shape? : Shape , highlight = false ) : string {
const shapeObj = ( shape && MARKER_SHAPES [ shape ] ) || MARKER_SHAPES . drop ! ;
const width = Math . ceil ( height * shapeObj . width / shapeObj . height ) ;
return "data:image/svg+xml," + encodeURIComponent (
` <?xml version="1.0" encoding="UTF-8" standalone="no"?> ` +
` <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width=" ${ width } " height=" ${ height } " viewBox="0 0 ${ shapeObj . width } ${ shapeObj . height } " version="1.1"> ` +
getMarkerCode ( colour , shapeObj . height , symbol , shape , highlight ) +
` </svg> `
) ;
}
2021-01-23 11:38:26 +00:00
2021-03-07 21:56:30 +00:00
export function getMarkerHtml ( colour : string , height : number , symbol ? : Symbol , shape? : Shape , highlight = false ) : string {
const shapeObj = ( shape && MARKER_SHAPES [ shape ] ) || MARKER_SHAPES . drop ! ;
const width = Math . ceil ( height * shapeObj . width / shapeObj . height ) ;
return (
` <svg width=" ${ width } " height=" ${ height } " viewBox="0 0 ${ shapeObj . width } ${ shapeObj . height } "> ` +
getMarkerCode ( colour , shapeObj . height , symbol , shape , highlight ) +
` </svg> `
) ;
2021-01-23 11:38:26 +00:00
}
2021-03-07 21:56:30 +00:00
export function getMarkerIcon ( colour : Colour , height : number , symbol ? : Symbol , shape? : Shape , highlight = false ) : Icon {
2021-02-28 22:17:26 +00:00
const scale = height / 31 ;
const shapeObj = ( shape && MARKER_SHAPES [ shape ] ) || MARKER_SHAPES . drop ! ;
2021-01-23 11:38:26 +00:00
return L . icon ( {
2021-03-07 21:56:30 +00:00
iconUrl : getMarkerUrl ( colour , height , symbol , shape , highlight ) ,
iconSize : [ shapeObj . width * scale , shapeObj . height * scale ] ,
iconAnchor : [ Math . round ( shapeObj . baseX * scale ) , Math . round ( shapeObj . baseY * scale ) ] ,
2021-01-23 11:38:26 +00:00
popupAnchor : [ 0 , - height ]
} ) ;
2021-02-28 22:17:26 +00:00
}