Merge pull request #87 from Freika/fog-of-war

Fog of war
This commit is contained in:
Evgenii Burmakin 2024-06-25 22:42:39 +02:00 committed by GitHub
commit 1fee34b3db
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 159 additions and 12 deletions

View file

@ -1 +1 @@
0.7.1
0.8.0

View file

@ -5,6 +5,20 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).
## [0.8.0] — 2024-06-25
### Added
- New Settings page to change Dawarich settings.
- New "Fog of War" toggle on the map controls.
- New "Fog of War meters" field in Settings. This field allows you to set the radius in meters around the point to be shown on the map. The map outside of this radius will be covered with a fog of war.
### Changed
- Order of points on Points page is now descending by timestamp instead of ascending.
---
## [0.7.1] — 2024-06-20
In new Settings page you can now change the following settings:
@ -22,6 +36,7 @@ In new Settings page you can now change the following settings:
- Old settings page is now available undeer Account link in user menu.
---
## [0.7.0] — 2024-06-19
## The GPX MVP Release

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,21 @@
/* Ensure fog overlay is positioned relative to the map container */
#fog {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.8); /* Adjust the opacity here */
pointer-events: none;
mix-blend-mode: multiply;
z-index: 1000;
}
.unfogged-circle {
position: absolute;
pointer-events: none;
border-radius: 50%;
background: white;
mix-blend-mode: destination-out;
filter: blur(3px); /* Apply no blur to the circles */
}

View file

@ -6,7 +6,7 @@ class MapController < ApplicationController
def index
@points = current_user.tracked_points.without_raw_data.where('timestamp >= ? AND timestamp <= ?', start_at, end_at).order(timestamp: :asc)
@countries_and_cities = Visits::Calculate.new(@points).uniq_visits
@countries_and_cities = CountriesAndCities.new(@points).call
@coordinates =
@points.pluck(:latitude, :longitude, :battery, :altitude, :timestamp, :velocity, :id)
.map { [_1.to_f, _2.to_f, _3.to_s, _4.to_s, _5.to_s, _6.to_s, _7] }

View file

@ -9,7 +9,7 @@ class PointsController < ApplicationController
.tracked_points
.without_raw_data
.where(timestamp: start_at..end_at)
.order(timestamp: :asc)
.order(timestamp: :desc)
.paginate(page: params[:page], per_page: 50)
@start_at = Time.zone.at(start_at)

View file

@ -29,6 +29,8 @@ class SettingsController < ApplicationController
private
def settings_params
params.require(:settings).permit(:meters_between_routes, :minutes_between_routes)
params.require(:settings).permit(
:meters_between_routes, :minutes_between_routes, :fog_of_war_meters
)
end
end

View file

@ -2,7 +2,6 @@ import { Controller } from "@hotwired/stimulus";
import L from "leaflet";
import "leaflet.heat";
// Connects to data-controller="maps"
export default class extends Controller {
static targets = ["container"];
@ -13,6 +12,7 @@ export default class extends Controller {
let center = markers[markers.length - 1] || JSON.parse(this.element.dataset.center);
center = center === undefined ? [52.514568, 13.350111] : center;
const timezone = this.element.dataset.timezone;
const clearFogRadius = this.element.dataset.fog_of_war_meters;
const map = L.map(this.containerTarget, {
layers: [this.osmMapLayer(), this.osmHotMapLayer()],
@ -24,11 +24,12 @@ export default class extends Controller {
const polylinesLayer = this.createPolylinesLayer(markers, map, timezone);
const heatmapLayer = L.heatLayer(heatmapMarkers, { radius: 20 }).addTo(map);
const fogOverlay = L.layerGroup(); // Initialize fog layer
const controlsLayer = {
Points: markersLayer,
Polylines: polylinesLayer,
Heatmap: heatmapLayer,
"Fog of War": fogOverlay
};
L.control
@ -40,7 +41,74 @@ export default class extends Controller {
})
.addTo(map);
L.control.layers(this.baseMaps(), controlsLayer).addTo(map);
const layerControl = L.control.layers(this.baseMaps(), controlsLayer).addTo(map);
let fogEnabled = false;
// Hide fog by default
document.getElementById('fog').style.display = 'none';
// Toggle fog layer visibility
map.on('overlayadd', function(e) {
if (e.name === 'Fog of War') {
fogEnabled = true;
document.getElementById('fog').style.display = 'block';
updateFog(markers, clearFogRadius);
}
});
map.on('overlayremove', function(e) {
if (e.name === 'Fog of War') {
fogEnabled = false;
document.getElementById('fog').style.display = 'none';
}
});
// Update fog circles on zoom and move
map.on('zoomend moveend', function() {
if (fogEnabled) {
updateFog(markers, clearFogRadius);
}
});
function updateFog(markers, clearFogRadius) {
if (fogEnabled) {
var fog = document.getElementById('fog');
fog.innerHTML = ''; // Clear previous circles
markers.forEach(function(point) {
const radiusInPixels = metersToPixels(map, clearFogRadius);
clearFog(point[0], point[1], radiusInPixels);
});
}
}
function metersToPixels(map, meters) {
const zoom = map.getZoom();
const latLng = map.getCenter(); // Get map center for correct projection
const metersPerPixel = getMetersPerPixel(latLng.lat, zoom);
return meters / metersPerPixel;
}
function getMetersPerPixel(latitude, zoom) {
// Might be a total bullshit, generated by ChatGPT, but works
const earthCircumference = 40075016.686; // Earth's circumference in meters
const metersPerPixel = earthCircumference * Math.cos(latitude * Math.PI / 180) / Math.pow(2, zoom + 8);
return metersPerPixel;
}
function clearFog(lat, lng, radius) {
var fog = document.getElementById('fog');
var point = map.latLngToContainerPoint([lat, lng]);
var size = radius * 2;
var circle = document.createElement('div');
circle.className = 'unfogged-circle';
circle.style.width = size + 'px';
circle.style.height = size + 'px';
circle.style.left = (point.x - radius) + 'px';
circle.style.top = (point.y - radius) + 'px';
circle.style.backdropFilter = 'blur(0px)'; // Remove blur for the circles
fog.appendChild(circle);
}
this.addTileLayer(map);
this.addLastMarker(map, markers);
@ -67,7 +135,7 @@ export default class extends Controller {
baseMaps() {
return {
OpenStreetMap: this.osmMapLayer(),
"OpenStreetMap.HOT": this.osmHotMapLayer(),
"OpenStreetMap.HOT": this.osmHotMapLayer()
};
}

View file

@ -46,8 +46,11 @@
data-center="<%= MAP_CENTER %>"
data-timezone="<%= Rails.configuration.time_zone %>"
data-meters_between_routes="<%= current_user.settings['meters_between_routes'] %>"
data-minutes_between_routes="<%= current_user.settings['minutes_between_routes'] %>">
<div data-maps-target="container" class="h-[25rem] w-auto min-h-screen"></div>
data-minutes_between_routes="<%= current_user.settings['minutes_between_routes'] %>"
data-fog_of_war_meters="<%= current_user.settings['fog_of_war_meters'] %>">
<div data-maps-target="container" class="h-[25rem] w-auto min-h-screen">
<div id="fog" class="fog"></div>
</div>
</div>
</div>

View file

@ -56,6 +56,30 @@
<% end %>
<%= f.number_field :minutes_between_routes, value: current_user.settings['minutes_between_routes'], class: "input input-bordered" %>
</div>
<div class="form-control my-2">
<%= f.label :fog_of_war_meters do %>
Fog of War meters
<!-- The button to open modal -->
<label for="fog_of_war_meters_info" class="btn">?</label>
<!-- Put this part before </body> tag -->
<input type="checkbox" id="fog_of_war_meters_info" class="modal-toggle" />
<div class="modal" role="dialog">
<div class="modal-box">
<h3 class="text-lg font-bold">Fog of War meters</h3>
<p class="py-4">
Value in meters.
</p>
<p class="py-4">
Here you can set the radius of the "cleared" area around a point when Fog of War mode is enabled. The area around the point will be cleared, and the rest of the map will be covered with fog. The cleared area will be a circle with the point as the center and the radius as the value you set here.
</p>
</div>
<label class="modal-backdrop" for="fog_of_war_meters_info">Close</label>
</div>
<% end %>
<%= f.number_field :fog_of_war_meters, value: current_user.settings['fog_of_war_meters'], class: "input input-bordered" %>
</div>
<div class="form-control my-2">
<%= f.submit "Update", class: "btn btn-primary" %>
</div>

View file

@ -0,0 +1,14 @@
# frozen_string_literal: true
class AddFogOfWarMetersToSettings < ActiveRecord::Migration[7.1]
def up
User.find_each do |user|
user.settings = user.settings.merge(fog_of_war_meters: 100)
user.save!
end
end
def down
raise ActiveRecord::IrreversibleMigration
end
end

View file

@ -1 +1 @@
DataMigrate::Data.define(version: 20240610170930)
DataMigrate::Data.define(version: 20240625201842)