mirror of
https://github.com/Freika/dawarich.git
synced 2026-01-09 08:47:11 -05:00
* fix: move foreman to global gems to fix startup crash (#1971) * Update exporting code to stream points data to file in batches to red… (#1980) * Update exporting code to stream points data to file in batches to reduce memory usage * Update changelog * Update changelog * Feature/maplibre frontend (#1953) * Add a plan to use MapLibre GL JS for the frontend map rendering, replacing Leaflet * Implement phase 1 * Phases 1-3 + part of 4 * Fix e2e tests * Phase 6 * Implement fog of war * Phase 7 * Next step: fix specs, phase 7 done * Use our own map tiles * Extract v2 map logic to separate manager classes * Update settings panel on v2 map * Update v2 e2e tests structure * Reimplement location search in maps v2 * Update speed routes * Implement visits and places creation in v2 * Fix last failing test * Implement visits merging * Fix a routes e2e test and simplify the routes layer styling. * Extract js to modules from maps_v2_controller.js * Implement area creation * Fix spec problem * Fix some e2e tests * Implement live mode in v2 map * Update icons and panel * Extract some styles * Remove unused file * Start adding dark theme to popups on MapLibre maps * Make popups respect dark theme * Move v2 maps to maplibre namespace * Update v2 references to maplibre * Put place, area and visit info into side panel * Update API to use safe settings config method * Fix specs * Fix method name to config in SafeSettings and update usages accordingly * Add missing public files * Add handling for real time points * Fix remembering enabled/disabled layers of the v2 map * Fix lots of e2e tests * Add settings to select map version * Use maps/v2 as main path for MapLibre maps * Update routing * Update live mode * Update maplibre controller * Update changelog * Remove some console.log statements --------- Co-authored-by: Robin Tuszik <mail@robin.gg>
140 lines
3.4 KiB
JavaScript
140 lines
3.4 KiB
JavaScript
/**
|
|
* Fog of war layer
|
|
* Shows explored vs unexplored areas using canvas overlay
|
|
* Does not extend BaseLayer as it uses canvas instead of MapLibre layers
|
|
*/
|
|
export class FogLayer {
|
|
constructor(map, options = {}) {
|
|
this.map = map
|
|
this.id = 'fog'
|
|
this.visible = options.visible !== undefined ? options.visible : false
|
|
this.canvas = null
|
|
this.ctx = null
|
|
this.clearRadius = options.clearRadius || 1000 // meters
|
|
this.points = []
|
|
}
|
|
|
|
add(data) {
|
|
this.points = data.features || []
|
|
this.createCanvas()
|
|
if (this.visible) {
|
|
this.show()
|
|
}
|
|
this.render()
|
|
}
|
|
|
|
update(data) {
|
|
this.points = data.features || []
|
|
this.render()
|
|
}
|
|
|
|
createCanvas() {
|
|
if (this.canvas) return
|
|
|
|
// Create canvas overlay
|
|
this.canvas = document.createElement('canvas')
|
|
this.canvas.className = 'fog-canvas'
|
|
this.canvas.style.position = 'absolute'
|
|
this.canvas.style.top = '0'
|
|
this.canvas.style.left = '0'
|
|
this.canvas.style.pointerEvents = 'none'
|
|
this.canvas.style.zIndex = '10'
|
|
this.canvas.style.display = this.visible ? 'block' : 'none'
|
|
|
|
this.ctx = this.canvas.getContext('2d')
|
|
|
|
// Add to map container
|
|
const mapContainer = this.map.getContainer()
|
|
mapContainer.appendChild(this.canvas)
|
|
|
|
// Update on map move/zoom/resize
|
|
this.map.on('move', () => this.render())
|
|
this.map.on('zoom', () => this.render())
|
|
this.map.on('resize', () => this.resizeCanvas())
|
|
|
|
this.resizeCanvas()
|
|
}
|
|
|
|
resizeCanvas() {
|
|
if (!this.canvas) return
|
|
|
|
const container = this.map.getContainer()
|
|
this.canvas.width = container.offsetWidth
|
|
this.canvas.height = container.offsetHeight
|
|
this.render()
|
|
}
|
|
|
|
render() {
|
|
if (!this.canvas || !this.ctx || !this.visible) return
|
|
|
|
const { width, height } = this.canvas
|
|
|
|
// Clear canvas
|
|
this.ctx.clearRect(0, 0, width, height)
|
|
|
|
// Draw fog overlay
|
|
this.ctx.fillStyle = 'rgba(0, 0, 0, 0.6)'
|
|
this.ctx.fillRect(0, 0, width, height)
|
|
|
|
// Clear circles around visited points
|
|
this.ctx.globalCompositeOperation = 'destination-out'
|
|
|
|
this.points.forEach(feature => {
|
|
const coords = feature.geometry.coordinates
|
|
const point = this.map.project(coords)
|
|
|
|
// Calculate pixel radius based on zoom level
|
|
const metersPerPixel = this.getMetersPerPixel(coords[1])
|
|
const radiusPixels = this.clearRadius / metersPerPixel
|
|
|
|
this.ctx.beginPath()
|
|
this.ctx.arc(point.x, point.y, radiusPixels, 0, Math.PI * 2)
|
|
this.ctx.fill()
|
|
})
|
|
|
|
this.ctx.globalCompositeOperation = 'source-over'
|
|
}
|
|
|
|
getMetersPerPixel(latitude) {
|
|
const earthCircumference = 40075017 // meters at equator
|
|
const latitudeRadians = latitude * Math.PI / 180
|
|
const zoom = this.map.getZoom()
|
|
return earthCircumference * Math.cos(latitudeRadians) / (256 * Math.pow(2, zoom))
|
|
}
|
|
|
|
show() {
|
|
this.visible = true
|
|
if (this.canvas) {
|
|
this.canvas.style.display = 'block'
|
|
this.render()
|
|
}
|
|
}
|
|
|
|
hide() {
|
|
this.visible = false
|
|
if (this.canvas) {
|
|
this.canvas.style.display = 'none'
|
|
}
|
|
}
|
|
|
|
toggle(visible = !this.visible) {
|
|
if (visible) {
|
|
this.show()
|
|
} else {
|
|
this.hide()
|
|
}
|
|
}
|
|
|
|
remove() {
|
|
if (this.canvas) {
|
|
this.canvas.remove()
|
|
this.canvas = null
|
|
this.ctx = null
|
|
}
|
|
|
|
// Remove event listeners
|
|
this.map.off('move', this.render)
|
|
this.map.off('zoom', this.render)
|
|
this.map.off('resize', this.resizeCanvas)
|
|
}
|
|
}
|