aboutsummaryrefslogtreecommitdiff
path: root/frontend/ts/src/pages/map/marker.ts
blob: 435ed5be8b242a9f4a638a5a3376686094c62700 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
import { Var, mount, h, s } from 'lib/rx'
import * as Color from 'lib/color'
import * as M from 'lib/leaflet'
import * as icons from 'lib/icons'
import * as markerModel from 'models/marker'

interface CreateParams {
    marker: markerModel.Marker
    onMove: (marker: M.Layer) => void
    onClick: (markerElem: M.FeatureGroup) => void
    readOnlyVar: Var<boolean>
}

export function create({ marker, onMove, onClick, readOnlyVar }: CreateParams): M.FeatureGroup {
    const { lat, lng, color, icon, name, description, radius } = marker
    const pos = { lat, lng }

    const markerElem = M.marker(pos, {
      draggable: true,
      autoPan: true,
      icon: divIcon({ icon, color, name, description })
    })

    const circle =
      radius !== undefined && radius !== 0
        ? M.circle(pos, { radius, color, fillColor: color })
        : undefined

    const layer =
      circle !== undefined
        ? M.featureGroup([ markerElem, circle ])
        : M.featureGroup([ markerElem ])

    // Fired before dragging, permits to disable dragging just at the right
    // moment if in readonly mode.
    markerElem.addEventListener('mousedown', () => {
        if (readOnlyVar.now()) {
            markerElem.dragging.disable()
            window.setTimeout(() => markerElem.dragging.enable())
        }
    })

    markerElem.addEventListener('drag', e => {
      circle && circle.setLatLng(markerElem.getLatLng())
    })

    markerElem.addEventListener('dragend', () => onMove(markerElem))

    markerElem.addEventListener('click', (e: M.MapEvent) => onClick(layer))

    return layer
}

interface CreateIconParams {
  icon?: string,
  color: string,
  name?: string,
  description?: string
}

function divIcon({ icon, color, name, description }: CreateIconParams): M.Icon {
    const c = Color.parse(color)
    const crBlack = Color.contrastRatio({ red: 0, green: 0, blue: 0 }, c)
    const crWhite = Color.contrastRatio({ red: 255, green: 255, blue: 255 }, c)
    const textCol = crBlack > crWhite ? 'black' : 'white'
    const width = 10
    const height = 15
    const stroke = 'black'
    const strokeWidth = 0.6
    // Triangle
    const t = [
      { x: width * 0.15, y: 7.46 },
      { x: width / 2, y: height },
      { x: width * 0.85, y: 7.46 }
    ]

    const html = document.createElement('div')
    html.className = 'g-Marker'
    if (description) html.title = description

    mount(html, [
        s('svg',
            {
                viewBox: `0 0 ${width} ${height}`,
                class: 'g-Marker__Base'
            },
            s('circle',
                {
                    cx: width / 2,
                    cy: width / 2,
                    r: (width - 2 * strokeWidth) / 2,
                    stroke,
                    'stroke-width': strokeWidth,
                    fill: color
                }
            ),
            s('polygon', { points: `${t[0].x},${t[0].y} ${t[1].x},${t[1].y} ${t[2].x},${t[2].y}`, fill: color }),
            s('line', {
                x1: t[0].x,
                y1: t[0].y,
                x2: t[1].x,
                y2: t[1].y,
                stroke,
                'stroke-width': strokeWidth
            }),
            s('line', {
                x1: t[1].x,
                y1: t[1].y,
                x2: t[2].x,
                y2: t[2].y,
                stroke,
                'stroke-width': strokeWidth
            }),
        ),
        icon && icons.svg(icon, {
            class: 'g-Marker__Icon',
            style: `fill: ${textCol}; stroke: ${textCol}`
        }),
        name && h('div',
            {
                className: 'g-Marker__Title',
                style: `text-shadow: ${textShadow('white', 1, 1)}`
            },
            name
        )
    ])

    return M.divIcon({
        className: '',
        popupAnchor: [ 0, -34 ],
        html
    })
}

function textShadow(color: string, w: number, blurr: number): string {
  return [[-w, -w], [-w, 0], [-w, w], [0, -w], [0, w], [w, -w], [w, 0], [w, w]]
    .map(xs => `${color} ${xs[0]}px ${xs[1]}px ${blurr}px`)
    .join(', ')
}