mirror of
https://github.com/Freika/dawarich.git
synced 2026-01-10 17:21:38 -05:00
Extract fog of war to a separate file
This commit is contained in:
parent
e457631715
commit
e25d6f05e2
3 changed files with 158 additions and 134 deletions
|
|
@ -16,33 +16,23 @@ import {
|
|||
import { fetchAndDrawAreas } from "../maps/areas";
|
||||
import { handleAreaCreated } from "../maps/areas";
|
||||
|
||||
import { showFlashMessage } from "../maps/helpers";
|
||||
import { fetchAndDisplayPhotos } from '../maps/helpers';
|
||||
import { showFlashMessage, fetchAndDisplayPhotos, debounce } from "../maps/helpers";
|
||||
|
||||
import { osmMapLayer } from "../maps/layers";
|
||||
import { osmHotMapLayer } from "../maps/layers";
|
||||
import { OPNVMapLayer } from "../maps/layers";
|
||||
import { openTopoMapLayer } from "../maps/layers";
|
||||
import { cyclOsmMapLayer } from "../maps/layers";
|
||||
import { esriWorldStreetMapLayer } from "../maps/layers";
|
||||
import { esriWorldTopoMapLayer } from "../maps/layers";
|
||||
import { esriWorldImageryMapLayer } from "../maps/layers";
|
||||
import { esriWorldGrayCanvasMapLayer } from "../maps/layers";
|
||||
import {
|
||||
osmMapLayer,
|
||||
osmHotMapLayer,
|
||||
OPNVMapLayer,
|
||||
openTopoMapLayer,
|
||||
cyclOsmMapLayer,
|
||||
esriWorldStreetMapLayer,
|
||||
esriWorldTopoMapLayer,
|
||||
esriWorldImageryMapLayer,
|
||||
esriWorldGrayCanvasMapLayer
|
||||
} from "../maps/layers";
|
||||
import { countryCodesMap } from "../maps/country_codes";
|
||||
|
||||
import "leaflet-draw";
|
||||
|
||||
function debounce(func, wait) {
|
||||
let timeout;
|
||||
return function executedFunction(...args) {
|
||||
const later = () => {
|
||||
clearTimeout(timeout);
|
||||
func(...args);
|
||||
};
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(later, wait);
|
||||
};
|
||||
}
|
||||
import { initializeFogCanvas, drawFogCanvas, createFogOverlay } from "../maps/fog_of_war";
|
||||
|
||||
export default class extends Controller {
|
||||
static targets = ["container"];
|
||||
|
|
@ -86,18 +76,7 @@ export default class extends Controller {
|
|||
this.heatmapLayer = L.heatLayer(this.heatmapMarkers, { radius: 20 }).addTo(this.map);
|
||||
|
||||
// Create a proper Leaflet layer for fog
|
||||
this.fogOverlay = L.Layer.extend({
|
||||
onAdd: (map) => {
|
||||
this.initializeFogCanvas();
|
||||
this.updateFog(this.markers, this.clearFogRadius);
|
||||
},
|
||||
onRemove: (map) => {
|
||||
const fog = document.getElementById('fog');
|
||||
if (fog) {
|
||||
fog.remove();
|
||||
}
|
||||
}
|
||||
});
|
||||
this.fogOverlay = createFogOverlay();
|
||||
|
||||
this.areasLayer = L.layerGroup(); // Initialize areas layer
|
||||
this.photoMarkers = L.layerGroup();
|
||||
|
|
@ -559,97 +538,9 @@ export default class extends Controller {
|
|||
updateFog(markers, clearFogRadius) {
|
||||
const fog = document.getElementById('fog');
|
||||
if (!fog) {
|
||||
this.initializeFogCanvas();
|
||||
initializeFogCanvas(this.map);
|
||||
}
|
||||
requestAnimationFrame(() => this.drawFogCanvas(markers, clearFogRadius));
|
||||
}
|
||||
|
||||
initializeFogCanvas() {
|
||||
// Remove existing fog canvas if it exists
|
||||
const oldFog = document.getElementById('fog');
|
||||
if (oldFog) oldFog.remove();
|
||||
|
||||
// Create new fog canvas
|
||||
const fog = document.createElement('canvas');
|
||||
fog.id = 'fog';
|
||||
fog.style.position = 'absolute';
|
||||
fog.style.top = '0';
|
||||
fog.style.left = '0';
|
||||
fog.style.pointerEvents = 'none';
|
||||
fog.style.zIndex = '400';
|
||||
|
||||
// Set canvas size to match map container
|
||||
const mapSize = this.map.getSize();
|
||||
fog.width = mapSize.x;
|
||||
fog.height = mapSize.y;
|
||||
|
||||
// Add canvas to map container
|
||||
this.map.getContainer().appendChild(fog);
|
||||
|
||||
// Add resize handler
|
||||
this.map.on('resize', () => {
|
||||
const newSize = this.map.getSize();
|
||||
fog.width = newSize.x;
|
||||
fog.height = newSize.y;
|
||||
this.drawFogCanvas(this.markers, this.clearFogRadius);
|
||||
});
|
||||
}
|
||||
|
||||
drawFogCanvas(markers, clearFogRadius) {
|
||||
const fog = document.getElementById('fog');
|
||||
if (!fog) return;
|
||||
|
||||
const ctx = fog.getContext('2d');
|
||||
if (!ctx) return;
|
||||
|
||||
const size = this.map.getSize();
|
||||
|
||||
// Clear the canvas
|
||||
ctx.clearRect(0, 0, size.x, size.y);
|
||||
|
||||
// Keep the light fog for unexplored areas
|
||||
ctx.fillStyle = 'rgba(0, 0, 0, 0.4)';
|
||||
ctx.fillRect(0, 0, size.x, size.y);
|
||||
|
||||
// Set up for "cutting" holes
|
||||
ctx.globalCompositeOperation = 'destination-out';
|
||||
|
||||
// Draw clear circles for each point
|
||||
markers.forEach(point => {
|
||||
const latLng = L.latLng(point[0], point[1]);
|
||||
const pixelPoint = this.map.latLngToContainerPoint(latLng);
|
||||
const radiusInPixels = this.metersToPixels(this.map, clearFogRadius);
|
||||
|
||||
// Make explored areas completely transparent
|
||||
const gradient = ctx.createRadialGradient(
|
||||
pixelPoint.x, pixelPoint.y, 0,
|
||||
pixelPoint.x, pixelPoint.y, radiusInPixels
|
||||
);
|
||||
gradient.addColorStop(0, 'rgba(255, 255, 255, 1)'); // 100% transparent
|
||||
gradient.addColorStop(0.85, 'rgba(255, 255, 255, 1)'); // Still 100% transparent
|
||||
gradient.addColorStop(1, 'rgba(255, 255, 255, 0)'); // Fade to fog at edge
|
||||
|
||||
ctx.fillStyle = gradient;
|
||||
ctx.beginPath();
|
||||
ctx.arc(pixelPoint.x, pixelPoint.y, radiusInPixels, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
});
|
||||
|
||||
// Reset composite operation
|
||||
ctx.globalCompositeOperation = 'source-over';
|
||||
}
|
||||
|
||||
metersToPixels(map, meters) {
|
||||
const zoom = map.getZoom();
|
||||
const latLng = map.getCenter();
|
||||
const metersPerPixel = this.getMetersPerPixel(latLng.lat, zoom);
|
||||
return meters / metersPerPixel;
|
||||
}
|
||||
|
||||
getMetersPerPixel(latitude, zoom) {
|
||||
const earthCircumference = 40075016.686;
|
||||
const metersPerPixel = earthCircumference * Math.cos(latitude * Math.PI / 180) / Math.pow(2, zoom + 8);
|
||||
return metersPerPixel;
|
||||
requestAnimationFrame(() => drawFogCanvas(this.map, markers, clearFogRadius));
|
||||
}
|
||||
|
||||
initializeDrawControl() {
|
||||
|
|
@ -911,6 +802,17 @@ export default class extends Controller {
|
|||
// Debounce the heavy operations
|
||||
const updateLayers = debounce(() => {
|
||||
try {
|
||||
// Store current layer visibility states
|
||||
const layerStates = {
|
||||
Points: this.map.hasLayer(this.markersLayer),
|
||||
Routes: this.map.hasLayer(this.polylinesLayer),
|
||||
Heatmap: this.map.hasLayer(this.heatmapLayer),
|
||||
"Fog of War": this.map.hasLayer(this.fogOverlay),
|
||||
"Scratch map": this.map.hasLayer(this.scratchLayer),
|
||||
Areas: this.map.hasLayer(this.areasLayer),
|
||||
Photos: this.map.hasLayer(this.photoMarkers)
|
||||
};
|
||||
|
||||
// Check if speed_colored_routes setting has changed
|
||||
if (newSettings.speed_colored_routes !== this.userSettings.speed_colored_routes) {
|
||||
if (this.polylinesLayer) {
|
||||
|
|
@ -934,19 +836,35 @@ export default class extends Controller {
|
|||
this.routeOpacity = parseFloat(newSettings.route_opacity) || 0.6;
|
||||
this.clearFogRadius = parseInt(newSettings.fog_of_war_meters) || 50;
|
||||
|
||||
// Update layer control
|
||||
this.map.removeControl(this.layerControl);
|
||||
// Remove existing layer control
|
||||
if (this.layerControl) {
|
||||
this.map.removeControl(this.layerControl);
|
||||
}
|
||||
|
||||
// Create new controls layer object with proper initialization
|
||||
const controlsLayer = {
|
||||
Points: this.markersLayer,
|
||||
Routes: this.polylinesLayer,
|
||||
Heatmap: this.heatmapLayer,
|
||||
"Fog of War": this.fogOverlay,
|
||||
"Scratch map": this.scratchLayer,
|
||||
Areas: this.areasLayer,
|
||||
Photos: this.photoMarkers
|
||||
Points: this.markersLayer || L.layerGroup(),
|
||||
Routes: this.polylinesLayer || L.layerGroup(),
|
||||
Heatmap: this.heatmapLayer || L.heatLayer([]),
|
||||
"Fog of War": new this.fogOverlay(),
|
||||
"Scratch map": this.scratchLayer || L.layerGroup(),
|
||||
Areas: this.areasLayer || L.layerGroup(),
|
||||
Photos: this.photoMarkers || L.layerGroup()
|
||||
};
|
||||
|
||||
// Add new layer control
|
||||
this.layerControl = L.control.layers(this.baseMaps(), controlsLayer).addTo(this.map);
|
||||
|
||||
// Restore layer visibility states
|
||||
Object.entries(layerStates).forEach(([name, wasVisible]) => {
|
||||
const layer = controlsLayer[name];
|
||||
if (wasVisible && layer) {
|
||||
layer.addTo(this.map);
|
||||
} else if (layer && this.map.hasLayer(layer)) {
|
||||
this.map.removeLayer(layer);
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error updating map settings:', error);
|
||||
console.error(error.stack);
|
||||
|
|
|
|||
94
app/javascript/maps/fog_of_war.js
Normal file
94
app/javascript/maps/fog_of_war.js
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
export function initializeFogCanvas(map) {
|
||||
// Remove existing fog canvas if it exists
|
||||
const oldFog = document.getElementById('fog');
|
||||
if (oldFog) oldFog.remove();
|
||||
|
||||
// Create new fog canvas
|
||||
const fog = document.createElement('canvas');
|
||||
fog.id = 'fog';
|
||||
fog.style.position = 'absolute';
|
||||
fog.style.top = '0';
|
||||
fog.style.left = '0';
|
||||
fog.style.pointerEvents = 'none';
|
||||
fog.style.zIndex = '400';
|
||||
|
||||
// Set canvas size to match map container
|
||||
const mapSize = map.getSize();
|
||||
fog.width = mapSize.x;
|
||||
fog.height = mapSize.y;
|
||||
|
||||
// Add canvas to map container
|
||||
map.getContainer().appendChild(fog);
|
||||
|
||||
return fog;
|
||||
}
|
||||
|
||||
export function drawFogCanvas(map, markers, clearFogRadius) {
|
||||
const fog = document.getElementById('fog');
|
||||
if (!fog) return;
|
||||
|
||||
const ctx = fog.getContext('2d');
|
||||
if (!ctx) return;
|
||||
|
||||
const size = map.getSize();
|
||||
|
||||
// Clear the canvas
|
||||
ctx.clearRect(0, 0, size.x, size.y);
|
||||
|
||||
// Keep the light fog for unexplored areas
|
||||
ctx.fillStyle = 'rgba(0, 0, 0, 0.4)';
|
||||
ctx.fillRect(0, 0, size.x, size.y);
|
||||
|
||||
// Set up for "cutting" holes
|
||||
ctx.globalCompositeOperation = 'destination-out';
|
||||
|
||||
// Draw clear circles for each point
|
||||
markers.forEach(point => {
|
||||
const latLng = L.latLng(point[0], point[1]);
|
||||
const pixelPoint = map.latLngToContainerPoint(latLng);
|
||||
const radiusInPixels = metersToPixels(map, clearFogRadius);
|
||||
|
||||
// Make explored areas completely transparent
|
||||
const gradient = ctx.createRadialGradient(
|
||||
pixelPoint.x, pixelPoint.y, 0,
|
||||
pixelPoint.x, pixelPoint.y, radiusInPixels
|
||||
);
|
||||
gradient.addColorStop(0, 'rgba(255, 255, 255, 1)'); // 100% transparent
|
||||
gradient.addColorStop(0.85, 'rgba(255, 255, 255, 1)'); // Still 100% transparent
|
||||
gradient.addColorStop(1, 'rgba(255, 255, 255, 0)'); // Fade to fog at edge
|
||||
|
||||
ctx.fillStyle = gradient;
|
||||
ctx.beginPath();
|
||||
ctx.arc(pixelPoint.x, pixelPoint.y, radiusInPixels, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
});
|
||||
|
||||
// Reset composite operation
|
||||
ctx.globalCompositeOperation = 'source-over';
|
||||
}
|
||||
|
||||
function metersToPixels(map, meters) {
|
||||
const zoom = map.getZoom();
|
||||
const latLng = map.getCenter();
|
||||
const metersPerPixel = getMetersPerPixel(latLng.lat, zoom);
|
||||
return meters / metersPerPixel;
|
||||
}
|
||||
|
||||
function getMetersPerPixel(latitude, zoom) {
|
||||
const earthCircumference = 40075016.686;
|
||||
return earthCircumference * Math.cos(latitude * Math.PI / 180) / Math.pow(2, zoom + 8);
|
||||
}
|
||||
|
||||
export function createFogOverlay() {
|
||||
return L.Layer.extend({
|
||||
onAdd: (map) => {
|
||||
initializeFogCanvas(map);
|
||||
},
|
||||
onRemove: (map) => {
|
||||
const fog = document.getElementById('fog');
|
||||
if (fog) {
|
||||
fog.remove();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -297,3 +297,15 @@ export function createPhotoMarker(photo, userSettings, photoMarkers, apiKey) {
|
|||
|
||||
photoMarkers.addLayer(marker);
|
||||
}
|
||||
|
||||
export function debounce(func, wait) {
|
||||
let timeout;
|
||||
return function executedFunction(...args) {
|
||||
const later = () => {
|
||||
clearTimeout(timeout);
|
||||
func(...args);
|
||||
};
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(later, wait);
|
||||
};
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue