From 5fa4d953f74544d22be8fb5ba0374996bc03ab17 Mon Sep 17 00:00:00 2001 From: MeijiRestored <42336759+MeijiRestored@users.noreply.github.com> Date: Wed, 14 May 2025 18:56:30 +0200 Subject: [PATCH 1/2] Improved fog of war --- app/javascript/maps/fog_of_war.js | 67 +++++++++++++++++++++---------- 1 file changed, 45 insertions(+), 22 deletions(-) diff --git a/app/javascript/maps/fog_of_war.js b/app/javascript/maps/fog_of_war.js index bc3acbb1..f46cb55e 100644 --- a/app/javascript/maps/fog_of_war.js +++ b/app/javascript/maps/fog_of_war.js @@ -33,38 +33,61 @@ export function drawFogCanvas(map, markers, clearFogRadius) { const size = map.getSize(); - // Clear the canvas + // 1) Paint base fog 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 + // 2) Cut out 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); + // 3) Build & sort points + const thresholdSec = 90; // points will be joined if < 1m30 of time difference + const pts = markers + .map(pt => { + const pixel = map.latLngToContainerPoint(L.latLng(pt[0], pt[1])); + return { pixel, time: parseInt(pt[4], 10) }; + }) + .sort((a, b) => a.time - b.time); - // 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 + const radiusPx = Math.max(metersToPixels(map, clearFogRadius), 2); + console.log(radiusPx); - ctx.fillStyle = gradient; - ctx.beginPath(); - ctx.arc(pixelPoint.x, pixelPoint.y, radiusInPixels, 0, Math.PI * 2); - ctx.fill(); + // 4) Mark which pts are part of a line + const connected = new Array(pts.length).fill(false); + for (let i = 0; i < pts.length - 1; i++) { + if (pts[i + 1].time - pts[i].time <= thresholdSec) { + connected[i] = true; + connected[i + 1] = true; + } + } + + // 5) Draw circles only for “alone” points + pts.forEach((pt, i) => { + if (!connected[i]) { + ctx.fillStyle = 'rgba(255,255,255,1)'; + ctx.beginPath(); + ctx.arc(pt.pixel.x, pt.pixel.y, radiusPx, 0, Math.PI * 2); + ctx.fill(); + } }); - // Reset composite operation + // 6) Draw rounded lines + ctx.lineWidth = radiusPx * 2; + ctx.lineCap = 'round'; + ctx.lineJoin = 'round'; + ctx.strokeStyle = 'rgba(255,255,255,1)'; + + for (let i = 0; i < pts.length - 1; i++) { + if (pts[i + 1].time - pts[i].time <= thresholdSec) { + ctx.beginPath(); + ctx.moveTo(pts[i].pixel.x, pts[i].pixel.y); + ctx.lineTo(pts[i + 1].pixel.x, pts[i + 1].pixel.y); + ctx.stroke(); + } + } + + // 7) Reset composite operation ctx.globalCompositeOperation = 'source-over'; } From e5075d59d3778ea5425cd358e9e3efaf97d53fc8 Mon Sep 17 00:00:00 2001 From: MeijiRestored <42336759+MeijiRestored@users.noreply.github.com> Date: Wed, 14 May 2025 21:04:47 +0200 Subject: [PATCH 2/2] configurable time threshold --- app/controllers/api/v1/settings_controller.rb | 2 +- app/javascript/controllers/maps_controller.js | 26 ++++++++++++------- app/javascript/maps/fog_of_war.js | 7 +++-- app/views/map/_settings_modals.html.erb | 14 ++++++++++ 4 files changed, 35 insertions(+), 14 deletions(-) diff --git a/app/controllers/api/v1/settings_controller.rb b/app/controllers/api/v1/settings_controller.rb index 4237e083..0471f49b 100644 --- a/app/controllers/api/v1/settings_controller.rb +++ b/app/controllers/api/v1/settings_controller.rb @@ -30,7 +30,7 @@ class Api::V1::SettingsController < ApiController :time_threshold_minutes, :merge_threshold_minutes, :route_opacity, :preferred_map_layer, :points_rendering_mode, :live_map_enabled, :immich_url, :immich_api_key, :photoprism_url, :photoprism_api_key, - :speed_colored_routes, :speed_color_scale + :speed_colored_routes, :speed_color_scale, :fog_of_war_threshold ) end end diff --git a/app/javascript/controllers/maps_controller.js b/app/javascript/controllers/maps_controller.js index 5c1eefab..c34c9de0 100644 --- a/app/javascript/controllers/maps_controller.js +++ b/app/javascript/controllers/maps_controller.js @@ -45,6 +45,7 @@ export default class extends BaseController { this.timezone = this.element.dataset.timezone; this.userSettings = JSON.parse(this.element.dataset.user_settings); this.clearFogRadius = parseInt(this.userSettings.fog_of_war_meters) || 50; + this.fogLinethreshold = parseInt(this.userSettings.fog_of_war_threshold) || 90; this.routeOpacity = parseFloat(this.userSettings.route_opacity) || 0.6; this.distanceUnit = this.element.dataset.distance_unit || "km"; this.pointsRenderingMode = this.userSettings.points_rendering_mode || "raw"; @@ -175,13 +176,13 @@ export default class extends BaseController { // Update event handlers this.map.on('moveend', () => { if (document.getElementById('fog')) { - this.updateFog(this.markers, this.clearFogRadius); + this.updateFog(this.markers, this.clearFogRadius, this.fogLinethreshold); } }); this.map.on('zoomend', () => { if (document.getElementById('fog')) { - this.updateFog(this.markers, this.clearFogRadius); + this.updateFog(this.markers, this.clearFogRadius, this.fogLinethreshold); } }); @@ -198,7 +199,7 @@ export default class extends BaseController { if (e.name === 'Fog of War') { fogEnabled = true; document.getElementById('fog').style.display = 'block'; - this.updateFog(this.markers, this.clearFogRadius); + this.updateFog(this.markers, this.clearFogRadius, this.fogLinethreshold); } }); @@ -212,7 +213,7 @@ export default class extends BaseController { // Update fog circles on zoom and move this.map.on('zoomend moveend', () => { if (fogEnabled) { - this.updateFog(this.markers, this.clearFogRadius); + this.updateFog(this.markers, this.clearFogRadius, this.fogLinethreshold); } }); @@ -350,7 +351,7 @@ export default class extends BaseController { // Update fog of war if enabled if (this.map.hasLayer(this.fogOverlay)) { - this.updateFog(this.markers, this.clearFogRadius); + this.updateFog(this.markers, this.clearFogRadius, this.fogLinethreshold); } // Update the last marker @@ -587,7 +588,7 @@ export default class extends BaseController { // Update fog if enabled if (this.map.hasLayer(this.fogOverlay)) { - this.updateFog(this.markers, this.clearFogRadius); + this.updateFog(this.markers, this.clearFogRadius, this.fogLinethreshold); } }) .catch(error => { @@ -623,12 +624,12 @@ export default class extends BaseController { } } - updateFog(markers, clearFogRadius) { + updateFog(markers, clearFogRadius, fogLinethreshold) { const fog = document.getElementById('fog'); if (!fog) { initializeFogCanvas(this.map); } - requestAnimationFrame(() => drawFogCanvas(this.map, markers, clearFogRadius)); + requestAnimationFrame(() => drawFogCanvas(this.map, markers, clearFogRadius, fogLinethreshold)); } initializeDrawControl() { @@ -724,7 +725,7 @@ export default class extends BaseController { // Form HTML div.innerHTML = ` -