diff options
Diffstat (limited to 'frontend/ts/src/pages/map/marker.ts')
-rw-r--r-- | frontend/ts/src/pages/map/marker.ts | 129 |
1 files changed, 129 insertions, 0 deletions
diff --git a/frontend/ts/src/pages/map/marker.ts b/frontend/ts/src/pages/map/marker.ts new file mode 100644 index 0000000..b690741 --- /dev/null +++ b/frontend/ts/src/pages/map/marker.ts @@ -0,0 +1,129 @@ +import { 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, +} + +export function create({ marker, onMove, onClick }: 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 ]) + + 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(', ') +} |