| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184 |
- import { CourseLayer } from '../layer/courseLayer'
- import { TrackLayer } from '../layer/trackLayer'
- import { GpsLayer } from '../layer/gpsLayer'
- import { TileLayer } from '../layer/tileLayer'
- import { TileStore, type TileStoreCallbacks } from '../tile/tileStore'
- import { type MapRenderer, type MapRendererStats, type MapScene } from './mapRenderer'
- import { WebGLTileRenderer } from './webglTileRenderer'
- import { WebGLVectorRenderer } from './webglVectorRenderer'
- import { CourseLabelRenderer } from './courseLabelRenderer'
- const RENDER_FRAME_MS = 16
- const ANIMATION_FRAME_MS = 33
- export class WebGLMapRenderer implements MapRenderer {
- tileStore: TileStore
- osmTileStore: TileStore
- tileLayer: TileLayer
- osmTileLayer: TileLayer
- courseLayer: CourseLayer
- trackLayer: TrackLayer
- gpsLayer: GpsLayer
- tileRenderer: WebGLTileRenderer
- vectorRenderer: WebGLVectorRenderer
- labelRenderer: CourseLabelRenderer
- scene: MapScene | null
- renderTimer: number
- animationTimer: number
- destroyed: boolean
- animationPaused: boolean
- pulseFrame: number
- lastStats: MapRendererStats
- onStats?: (stats: MapRendererStats) => void
- onTileError?: (message: string) => void
- constructor(onStats?: (stats: MapRendererStats) => void, onTileError?: (message: string) => void) {
- this.onStats = onStats
- this.onTileError = onTileError
- this.tileStore = new TileStore({
- onTileReady: () => {
- this.scheduleRender()
- },
- onTileError: (message) => {
- if (this.onTileError) {
- this.onTileError(message)
- }
- this.scheduleRender()
- },
- } satisfies TileStoreCallbacks)
- this.osmTileStore = new TileStore({
- onTileReady: () => {
- this.scheduleRender()
- },
- onTileError: () => {
- this.scheduleRender()
- },
- } satisfies TileStoreCallbacks)
- this.tileLayer = new TileLayer()
- this.osmTileLayer = new TileLayer()
- this.courseLayer = new CourseLayer()
- this.trackLayer = new TrackLayer()
- this.gpsLayer = new GpsLayer()
- this.tileRenderer = new WebGLTileRenderer(this.tileLayer, this.tileStore, this.osmTileLayer, this.osmTileStore)
- this.vectorRenderer = new WebGLVectorRenderer(this.courseLayer, this.trackLayer, this.gpsLayer)
- this.labelRenderer = new CourseLabelRenderer(this.courseLayer)
- this.scene = null
- this.renderTimer = 0
- this.animationTimer = 0
- this.destroyed = false
- this.animationPaused = false
- this.pulseFrame = 0
- this.lastStats = {
- visibleTileCount: 0,
- readyTileCount: 0,
- memoryTileCount: 0,
- diskTileCount: 0,
- memoryHitCount: 0,
- diskHitCount: 0,
- networkFetchCount: 0,
- }
- }
- attachCanvas(canvasNode: any, width: number, height: number, dpr: number, labelCanvasNode?: any): void {
- this.tileRenderer.attachCanvas(canvasNode, width, height, dpr)
- this.vectorRenderer.attachContext(this.tileRenderer.gl, canvasNode)
- if (labelCanvasNode) {
- this.labelRenderer.attachCanvas(labelCanvasNode, width, height, dpr)
- }
- this.startAnimation()
- this.scheduleRender()
- }
- updateScene(scene: MapScene): void {
- this.scene = scene
- this.scheduleRender()
- }
- setAnimationPaused(paused: boolean): void {
- this.animationPaused = paused
- if (!paused) {
- this.scheduleRender()
- }
- }
- destroy(): void {
- this.destroyed = true
- if (this.renderTimer) {
- clearTimeout(this.renderTimer)
- this.renderTimer = 0
- }
- if (this.animationTimer) {
- clearTimeout(this.animationTimer)
- this.animationTimer = 0
- }
- this.labelRenderer.destroy()
- this.vectorRenderer.destroy()
- this.tileRenderer.destroy()
- this.tileStore.destroy()
- this.osmTileStore.destroy()
- this.scene = null
- }
- startAnimation(): void {
- if (this.animationTimer) {
- return
- }
- const tick = () => {
- if (this.destroyed) {
- this.animationTimer = 0
- return
- }
- if (!this.animationPaused) {
- this.pulseFrame = (this.pulseFrame + 1) % 360
- this.scheduleRender()
- }
- this.animationTimer = setTimeout(tick, ANIMATION_FRAME_MS) as unknown as number
- }
- tick()
- }
- scheduleRender(): void {
- if (this.renderTimer || !this.scene || this.destroyed) {
- return
- }
- this.renderTimer = setTimeout(() => {
- this.renderTimer = 0
- this.renderFrame()
- }, RENDER_FRAME_MS) as unknown as number
- }
- renderFrame(): void {
- if (!this.scene) {
- return
- }
- this.tileRenderer.render(this.scene)
- this.vectorRenderer.render(this.scene, this.pulseFrame)
- this.labelRenderer.render(this.scene)
- this.emitStats(this.tileStore.getStats(this.tileLayer.lastVisibleTileCount, this.tileLayer.lastReadyTileCount))
- }
- emitStats(stats: MapRendererStats): void {
- if (
- stats.visibleTileCount === this.lastStats.visibleTileCount
- && stats.readyTileCount === this.lastStats.readyTileCount
- && stats.memoryTileCount === this.lastStats.memoryTileCount
- && stats.diskTileCount === this.lastStats.diskTileCount
- && stats.memoryHitCount === this.lastStats.memoryHitCount
- && stats.diskHitCount === this.lastStats.diskHitCount
- && stats.networkFetchCount === this.lastStats.networkFetchCount
- ) {
- return
- }
- this.lastStats = stats
- if (this.onStats) {
- this.onStats(stats)
- }
- }
- }
|