Allow users to delete points from the map

This commit is contained in:
Eugene Burmakin 2024-07-21 16:45:29 +02:00
parent 66e1feaf29
commit 4371d28ef7
6 changed files with 139 additions and 69 deletions

View file

@ -8,6 +8,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
## [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
- 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

View 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

View file

@ -4,12 +4,13 @@ class MapController < ApplicationController
before_action :authenticate_user!
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
@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] }
.map { [_1.to_f, _2.to_f, _3.to_s, _4.to_s, _5.to_s, _6.to_s, _7.to_s] }
@distance = distance
@start_at = Time.zone.at(start_at)
@end_at = Time.zone.at(end_at)

View file

@ -16,28 +16,29 @@ export default class extends Controller {
connect() {
console.log("Map controller connected");
const markers = JSON.parse(this.element.dataset.coordinates);
// The default map center is Victory Column in Berlin
let center = markers[markers.length - 1] || [52.514568, 13.350111];
const timezone = this.element.dataset.timezone;
const clearFogRadius = this.element.dataset.fog_of_war_meters;
this.markers = JSON.parse(this.element.dataset.coordinates);
this.timezone = this.element.dataset.timezone;
this.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()],
}).setView([center[0], center[1]], 14);
}).setView([this.center[0], this.center[1]], 14);
const markersArray = this.createMarkersArray(markers);
const markersLayer = L.layerGroup(markersArray);
const heatmapMarkers = markers.map((element) => [element[0], element[1], 0.3]);
this.markersArray = this.createMarkersArray(this.markers);
this.markersLayer = L.layerGroup(this.markersArray);
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 = {
Points: markersLayer,
Polylines: polylinesLayer,
Heatmap: heatmapLayer,
"Fog of War": fogOverlay,
Points: this.markersLayer,
Polylines: this.polylinesLayer,
Heatmap: this.heatmapLayer,
"Fog of War": this.fogOverlay,
};
L.control
@ -47,9 +48,9 @@ export default class extends Controller {
imperial: false,
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;
@ -57,15 +58,15 @@ export default class extends Controller {
document.getElementById('fog').style.display = 'none';
// Toggle fog layer visibility
map.on('overlayadd', function (e) {
this.map.on('overlayadd', (e) => {
if (e.name === 'Fog of War') {
fogEnabled = true;
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') {
fogEnabled = false;
document.getElementById('fog').style.display = 'none';
@ -73,53 +74,15 @@ export default class extends Controller {
});
// Update fog circles on zoom and move
map.on('zoomend moveend', function () {
this.map.on('zoomend moveend', () => {
if (fogEnabled) {
updateFog(markers, clearFogRadius);
this.updateFog(this.markers, this.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);
}
addTileLayer(map);
this.addLastMarker(map, markers);
addTileLayer(this.map);
this.addLastMarker(this.map, this.markers);
this.addEventListeners();
}
disconnect() {
@ -149,10 +112,62 @@ export default class extends Controller {
<b>Longitude:</b> ${marker[1]}<br>
<b>Altitude:</b> ${marker[3]}m<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) {
if (markers.length > 0) {
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) {
const originalStyle = { color: "blue", opacity: 0.6, weight: 3 };
const highlightStyle = { color: "yellow", opacity: 1, weight: 5 };

View file

@ -54,6 +54,8 @@ Rails.application.routes.draw do
namespace :api do
namespace :v1 do
resources :points, only: %i[destroy]
namespace :overland do
resources :batches, only: :create
end