mirror of
https://github.com/Freika/dawarich.git
synced 2026-01-11 01:31:39 -05:00
Allow users to delete points from the map
This commit is contained in:
parent
66e1feaf29
commit
4371d28ef7
6 changed files with 139 additions and 69 deletions
|
|
@ -8,6 +8,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||||
|
|
||||||
## [0.9.4] — 2024-07-21
|
## [0.9.4] — 2024-07-21
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- A popup being shown when user clicks on a point now contains a link to delete the point. This is useful if you want to delete a point that was imported by mistake or you just want to clean up your data.
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Added `public/imports` and `public/exports` folders to git to prevent errors when exporting data
|
- Added `public/imports` and `public/exports` folders to git to prevent errors when exporting data
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
12
app/controllers/api/v1/points_controller.rb
Normal file
12
app/controllers/api/v1/points_controller.rb
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class Api::V1::PointsController < ApplicationController
|
||||||
|
before_action :authenticate_user!
|
||||||
|
|
||||||
|
def destroy
|
||||||
|
point = current_user.points.find(params[:id])
|
||||||
|
point.destroy
|
||||||
|
|
||||||
|
render json: { message: 'Point deleted successfully' }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -4,12 +4,13 @@ class MapController < ApplicationController
|
||||||
before_action :authenticate_user!
|
before_action :authenticate_user!
|
||||||
|
|
||||||
def index
|
def index
|
||||||
@points = current_user.tracked_points.without_raw_data.where('timestamp >= ? AND timestamp <= ?', start_at, end_at).order(timestamp: :asc)
|
@points = current_user.tracked_points.without_raw_data.where('timestamp >= ? AND timestamp <= ?', start_at,
|
||||||
|
end_at).order(timestamp: :asc)
|
||||||
|
|
||||||
@countries_and_cities = CountriesAndCities.new(@points).call
|
@countries_and_cities = CountriesAndCities.new(@points).call
|
||||||
@coordinates =
|
@coordinates =
|
||||||
@points.pluck(:latitude, :longitude, :battery, :altitude, :timestamp, :velocity, :id)
|
@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] }
|
.map { [_1.to_f, _2.to_f, _3.to_s, _4.to_s, _5.to_s, _6.to_s, _7.to_s] }
|
||||||
@distance = distance
|
@distance = distance
|
||||||
@start_at = Time.zone.at(start_at)
|
@start_at = Time.zone.at(start_at)
|
||||||
@end_at = Time.zone.at(end_at)
|
@end_at = Time.zone.at(end_at)
|
||||||
|
|
|
||||||
|
|
@ -16,28 +16,29 @@ export default class extends Controller {
|
||||||
connect() {
|
connect() {
|
||||||
console.log("Map controller connected");
|
console.log("Map controller connected");
|
||||||
|
|
||||||
const markers = JSON.parse(this.element.dataset.coordinates);
|
this.markers = JSON.parse(this.element.dataset.coordinates);
|
||||||
// The default map center is Victory Column in Berlin
|
this.timezone = this.element.dataset.timezone;
|
||||||
let center = markers[markers.length - 1] || [52.514568, 13.350111];
|
this.clearFogRadius = this.element.dataset.fog_of_war_meters;
|
||||||
const timezone = this.element.dataset.timezone;
|
|
||||||
const clearFogRadius = this.element.dataset.fog_of_war_meters;
|
|
||||||
|
|
||||||
const map = L.map(this.containerTarget, {
|
this.center = this.markers[this.markers.length - 1] || [52.514568, 13.350111];
|
||||||
|
|
||||||
|
this.map = L.map(this.containerTarget, {
|
||||||
layers: [osmMapLayer(), osmHotMapLayer()],
|
layers: [osmMapLayer(), osmHotMapLayer()],
|
||||||
}).setView([center[0], center[1]], 14);
|
}).setView([this.center[0], this.center[1]], 14);
|
||||||
|
|
||||||
const markersArray = this.createMarkersArray(markers);
|
this.markersArray = this.createMarkersArray(this.markers);
|
||||||
const markersLayer = L.layerGroup(markersArray);
|
this.markersLayer = L.layerGroup(this.markersArray);
|
||||||
const heatmapMarkers = markers.map((element) => [element[0], element[1], 0.3]);
|
this.heatmapMarkers = this.markers.map((element) => [element[0], element[1], 0.3]);
|
||||||
|
|
||||||
|
this.polylinesLayer = this.createPolylinesLayer(this.markers, this.map, this.timezone);
|
||||||
|
this.heatmapLayer = L.heatLayer(this.heatmapMarkers, { radius: 20 }).addTo(this.map);
|
||||||
|
this.fogOverlay = L.layerGroup(); // Initialize fog layer
|
||||||
|
|
||||||
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 = {
|
const controlsLayer = {
|
||||||
Points: markersLayer,
|
Points: this.markersLayer,
|
||||||
Polylines: polylinesLayer,
|
Polylines: this.polylinesLayer,
|
||||||
Heatmap: heatmapLayer,
|
Heatmap: this.heatmapLayer,
|
||||||
"Fog of War": fogOverlay,
|
"Fog of War": this.fogOverlay,
|
||||||
};
|
};
|
||||||
|
|
||||||
L.control
|
L.control
|
||||||
|
|
@ -47,9 +48,9 @@ export default class extends Controller {
|
||||||
imperial: false,
|
imperial: false,
|
||||||
maxWidth: 120,
|
maxWidth: 120,
|
||||||
})
|
})
|
||||||
.addTo(map);
|
.addTo(this.map);
|
||||||
|
|
||||||
L.control.layers(this.baseMaps(), controlsLayer).addTo(map);
|
L.control.layers(this.baseMaps(), controlsLayer).addTo(this.map);
|
||||||
|
|
||||||
let fogEnabled = false;
|
let fogEnabled = false;
|
||||||
|
|
||||||
|
|
@ -57,15 +58,15 @@ export default class extends Controller {
|
||||||
document.getElementById('fog').style.display = 'none';
|
document.getElementById('fog').style.display = 'none';
|
||||||
|
|
||||||
// Toggle fog layer visibility
|
// Toggle fog layer visibility
|
||||||
map.on('overlayadd', function (e) {
|
this.map.on('overlayadd', (e) => {
|
||||||
if (e.name === 'Fog of War') {
|
if (e.name === 'Fog of War') {
|
||||||
fogEnabled = true;
|
fogEnabled = true;
|
||||||
document.getElementById('fog').style.display = 'block';
|
document.getElementById('fog').style.display = 'block';
|
||||||
updateFog(markers, clearFogRadius);
|
this.updateFog(this.markers, this.clearFogRadius);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
map.on('overlayremove', function (e) {
|
this.map.on('overlayremove', (e) => {
|
||||||
if (e.name === 'Fog of War') {
|
if (e.name === 'Fog of War') {
|
||||||
fogEnabled = false;
|
fogEnabled = false;
|
||||||
document.getElementById('fog').style.display = 'none';
|
document.getElementById('fog').style.display = 'none';
|
||||||
|
|
@ -73,53 +74,15 @@ export default class extends Controller {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Update fog circles on zoom and move
|
// Update fog circles on zoom and move
|
||||||
map.on('zoomend moveend', function () {
|
this.map.on('zoomend moveend', () => {
|
||||||
if (fogEnabled) {
|
if (fogEnabled) {
|
||||||
updateFog(markers, clearFogRadius);
|
this.updateFog(this.markers, this.clearFogRadius);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
function updateFog(markers, clearFogRadius) {
|
addTileLayer(this.map);
|
||||||
if (fogEnabled) {
|
this.addLastMarker(this.map, this.markers);
|
||||||
var fog = document.getElementById('fog');
|
this.addEventListeners();
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
addTileLayer(map);
|
|
||||||
this.addLastMarker(map, markers);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
disconnect() {
|
disconnect() {
|
||||||
|
|
@ -149,10 +112,62 @@ export default class extends Controller {
|
||||||
<b>Longitude:</b> ${marker[1]}<br>
|
<b>Longitude:</b> ${marker[1]}<br>
|
||||||
<b>Altitude:</b> ${marker[3]}m<br>
|
<b>Altitude:</b> ${marker[3]}m<br>
|
||||||
<b>Velocity:</b> ${marker[5]}km/h<br>
|
<b>Velocity:</b> ${marker[5]}km/h<br>
|
||||||
<b>Battery:</b> ${marker[2]}%
|
<b>Battery:</b> ${marker[2]}%<br>
|
||||||
|
<a href="#" data-id="${marker[6]}" class="delete-point">[Delete]</a>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addEventListeners() {
|
||||||
|
document.addEventListener('click', (event) => {
|
||||||
|
if (event.target && event.target.classList.contains('delete-point')) {
|
||||||
|
event.preventDefault();
|
||||||
|
const pointId = event.target.getAttribute('data-id');
|
||||||
|
|
||||||
|
if (confirm('Are you sure you want to delete this point?')) {
|
||||||
|
this.deletePoint(pointId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
deletePoint(id) {
|
||||||
|
fetch(`/api/v1/points/${id}`, {
|
||||||
|
method: 'DELETE',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]').getAttribute('content')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(response => {
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Network response was not ok');
|
||||||
|
}
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then(data => {
|
||||||
|
console.log('Point deleted:', data);
|
||||||
|
|
||||||
|
// Remove the marker from the map
|
||||||
|
this.removeMarker(id);
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('There was a problem with the delete request:', error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
removeMarker(id) {
|
||||||
|
const markerIndex = this.markersArray.findIndex(marker => marker.getPopup().getContent().includes(`data-id="${id}"`));
|
||||||
|
if (markerIndex !== -1) {
|
||||||
|
this.markersArray[markerIndex].remove(); // Assuming your marker object has a remove method
|
||||||
|
this.markersArray.splice(markerIndex, 1);
|
||||||
|
this.markersLayer.clearLayers();
|
||||||
|
this.markersLayer.addLayer(L.layerGroup(this.markersArray));
|
||||||
|
|
||||||
|
// Remove from the markers data array
|
||||||
|
this.markers = this.markers.filter(marker => marker[6] !== parseInt(id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
addLastMarker(map, markers) {
|
addLastMarker(map, markers) {
|
||||||
if (markers.length > 0) {
|
if (markers.length > 0) {
|
||||||
const lastMarker = markers[markers.length - 1].slice(0, 2);
|
const lastMarker = markers[markers.length - 1].slice(0, 2);
|
||||||
|
|
@ -160,6 +175,42 @@ export default class extends Controller {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateFog(markers, clearFogRadius) {
|
||||||
|
var fog = document.getElementById('fog');
|
||||||
|
fog.innerHTML = ''; // Clear previous circles
|
||||||
|
markers.forEach((point) => {
|
||||||
|
const radiusInPixels = this.metersToPixels(this.map, clearFogRadius);
|
||||||
|
this.clearFog(point[0], point[1], radiusInPixels);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
metersToPixels(map, meters) {
|
||||||
|
const zoom = map.getZoom();
|
||||||
|
const latLng = map.getCenter(); // Get map center for correct projection
|
||||||
|
const metersPerPixel = this.getMetersPerPixel(latLng.lat, zoom);
|
||||||
|
return meters / metersPerPixel;
|
||||||
|
}
|
||||||
|
|
||||||
|
getMetersPerPixel(latitude, zoom) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
clearFog(lat, lng, radius) {
|
||||||
|
var fog = document.getElementById('fog');
|
||||||
|
var point = this.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);
|
||||||
|
}
|
||||||
|
|
||||||
addHighlightOnHover(polyline, map, startPoint, endPoint, prevPoint, nextPoint, timezone) {
|
addHighlightOnHover(polyline, map, startPoint, endPoint, prevPoint, nextPoint, timezone) {
|
||||||
const originalStyle = { color: "blue", opacity: 0.6, weight: 3 };
|
const originalStyle = { color: "blue", opacity: 0.6, weight: 3 };
|
||||||
const highlightStyle = { color: "yellow", opacity: 1, weight: 5 };
|
const highlightStyle = { color: "yellow", opacity: 1, weight: 5 };
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,8 @@ Rails.application.routes.draw do
|
||||||
|
|
||||||
namespace :api do
|
namespace :api do
|
||||||
namespace :v1 do
|
namespace :v1 do
|
||||||
|
resources :points, only: %i[destroy]
|
||||||
|
|
||||||
namespace :overland do
|
namespace :overland do
|
||||||
resources :batches, only: :create
|
resources :batches, only: :create
|
||||||
end
|
end
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue