UNPKG

vue-maplibre-gl

Version:

Vue 3 plugin for maplibre-gl

220 lines (193 loc) 6.33 kB
import { Position, type PositionProp, PositionValues } from '@/components/controls/position.enum'; import { usePositionWatcher } from '@/composable/usePositionWatcher'; import { isInitializedSymbol, mapSymbol } from '@/types'; import type { IControl, Map as MMap } from 'maplibre-gl'; import { defineComponent, inject, onBeforeUnmount, type PropType } from 'vue'; export class FrameRateControl implements IControl { private frames = 0; private totalTime = 0; private totalFrames = 0; private time: number | null = null; private map?: MMap; private container?: HTMLDivElement; private readOutput?: HTMLDivElement; private canvas?: HTMLCanvasElement; private eventHandlers = new Map<string, Function>(); constructor(private background = 'rgba(0,0,0,0.9)', private barWidth = 4 * window.devicePixelRatio, private color = '#7cf859', private font = 'Monaco, Consolas, Courier, monospace', private graphHeight = 60 * window.devicePixelRatio, private graphWidth = 90 * window.devicePixelRatio, private graphTop = 0, private graphRight = 5 * window.devicePixelRatio, private width = 100 * window.devicePixelRatio) { } getDefaultPosition(): Position { return Position.TOP_RIGHT; } onAdd(map: MMap): HTMLElement { this.map = map; const el = (this.container = document.createElement('div')); el.className = 'maplibregl-ctrl maplibregl-ctrl-fps'; el.style.backgroundColor = this.background; el.style.borderRadius = '6px'; this.readOutput = document.createElement('div'); this.readOutput.style.color = this.color; this.readOutput.style.fontFamily = this.font; this.readOutput.style.padding = '0 5px 5px'; this.readOutput.style.fontSize = '9px'; this.readOutput.style.fontWeight = 'bold'; this.readOutput.textContent = 'Waiting…'; this.canvas = document.createElement('canvas'); this.canvas.className = 'maplibregl-ctrl-canvas'; this.canvas.width = this.width; this.canvas.height = this.graphHeight; this.canvas.style.cssText = `width: ${this.width / window.devicePixelRatio}px; height: ${this.graphHeight / window.devicePixelRatio}px;`; el.appendChild(this.readOutput); el.appendChild(this.canvas); this.eventHandlers.set('movestart', this.onMoveStart.bind(this)); this.eventHandlers.set('moveend', this.onMoveEnd.bind(this)); this.map.on('movestart', this.eventHandlers.get('movestart') as any); this.map.on('moveend', this.eventHandlers.get('moveend') as any); return this.container; } onRemove(): void { this.map!.off('movestart', this.eventHandlers.get('movestart') as any); this.map!.off('moveend', this.eventHandlers.get('moveend') as any); this.eventHandlers.clear(); this.container!.parentNode!.removeChild(this.container!); this.map = undefined; } onMoveStart() { this.frames = 0; this.time = performance.now(); this.eventHandlers.set('render', this.onRender.bind(this)); this.map!.on('render', this.eventHandlers.get('render') as any); } onMoveEnd() { const now = performance.now(); this.updateGraph(this.getFPS(now)); this.frames = 0; this.time = null; this.map!.off('render', this.eventHandlers.get('render') as any); } onRender() { if (this.time) { this.frames++; const now = performance.now(); if (now >= this.time + 1e3) { this.updateGraph(this.getFPS(now)); this.frames = 0; this.time = performance.now(); } } } getFPS(now: number) { this.totalTime += now - this.time!; this.totalFrames += this.frames; return Math.round((1e3 * this.frames) / (now - this.time!)) || 0; } updateGraph(fpsNow: number) { const context = this.canvas!.getContext('2d')!; const fps = Math.round((1e3 * this.totalFrames) / this.totalTime) || 0; const rect = (this.graphHeight, this.barWidth); context.fillStyle = this.background; context.globalAlpha = 1; context.fillRect(0, 0, this.graphWidth, this.graphTop); context.fillStyle = this.color; this.readOutput!.textContent = `${fpsNow} FPS (${fps} Avg)`; context.drawImage( this.canvas!, this.graphRight + rect, this.graphTop, this.graphWidth - rect, this.graphHeight, this.graphRight, this.graphTop, this.graphWidth - rect, this.graphHeight ); context.fillRect( this.graphRight + this.graphWidth - rect, this.graphTop, rect, this.graphHeight ); context.fillStyle = this.background; context.globalAlpha = 0.75; context.fillRect( this.graphRight + this.graphWidth - rect, this.graphTop, rect, (1 - fpsNow / 100) * this.graphHeight ); } } export default /*#__PURE__*/ defineComponent({ name : 'MglFrameRateControl', props: { position : { type : String as PropType<PositionProp>, validator: (v: Position) => { return PositionValues.indexOf(v) !== -1; } }, background : { type : String as PropType<string>, default: 'rgba(0,0,0,0.9)' }, barWidth : { type : Number as PropType<number>, default: 4 * window.devicePixelRatio }, color : { type : String as PropType<string>, default: '#7cf859' }, font : { type : String as PropType<string>, default: 'Monaco, Consolas, Courier, monospace' }, graphHeight: { type : Number as PropType<number>, default: 60 * window.devicePixelRatio }, graphWidth : { type : Number as PropType<number>, default: 90 * window.devicePixelRatio }, graphTop : { type : Number as PropType<number>, default: 0 }, graphRight : { type : Number as PropType<number>, default: 5 * window.devicePixelRatio }, width : { type : Number as PropType<number>, default: 100 * window.devicePixelRatio } }, setup(props) { const map = inject(mapSymbol)!, isInitialized = inject(isInitializedSymbol)!, control = new FrameRateControl( props.background, props.barWidth, props.color, props.font, props.graphHeight, props.graphWidth, props.graphTop, props.graphRight, props.width ); usePositionWatcher(() => props.position, map, control); onBeforeUnmount(() => isInitialized.value && map.value?.removeControl(control)); }, render() { // nothing } });