diff --git a/app/controllers/api/v1/points_controller.rb b/app/controllers/api/v1/points_controller.rb index a70dabdc..7905ca68 100644 --- a/app/controllers/api/v1/points_controller.rb +++ b/app/controllers/api/v1/points_controller.rb @@ -21,6 +21,14 @@ class Api::V1::PointsController < ApiController render json: serialized_points end + def update + point = current_api_user.tracked_points.find(params[:id]) + + point.update(point_params) + + render json: point_serializer.new(point).call + end + def destroy point = current_api_user.tracked_points.find(params[:id]) point.destroy @@ -30,6 +38,10 @@ class Api::V1::PointsController < ApiController private + def point_params + params.require(:point).permit(:latitude, :longitude) + end + def point_serializer params[:slim] == 'true' ? Api::SlimPointSerializer : Api::PointSerializer end diff --git a/app/javascript/controllers/maps_controller.js b/app/javascript/controllers/maps_controller.js index 01fa6ad7..8a0afdf0 100644 --- a/app/javascript/controllers/maps_controller.js +++ b/app/javascript/controllers/maps_controller.js @@ -68,7 +68,7 @@ export default class extends Controller { this.map.setMaxBounds(bounds); - this.markersArray = createMarkersArray(this.markers, this.userSettings); + this.markersArray = createMarkersArray(this.markers, this.userSettings, this.apiKey); this.markersLayer = L.layerGroup(this.markersArray); this.heatmapMarkers = this.markersArray.map((element) => [element._latlng.lat, element._latlng.lng, 0.2]); diff --git a/app/javascript/maps/markers.js b/app/javascript/maps/markers.js index cb319cea..d4e67380 100644 --- a/app/javascript/maps/markers.js +++ b/app/javascript/maps/markers.js @@ -1,18 +1,18 @@ import { createPopupContent } from "./popups"; -export function createMarkersArray(markersData, userSettings) { +export function createMarkersArray(markersData, userSettings, apiKey) { // Create a canvas renderer const renderer = L.canvas({ padding: 0.5 }); if (userSettings.pointsRenderingMode === "simplified") { return createSimplifiedMarkers(markersData, renderer); } else { - return markersData.map((marker) => { + return markersData.map((marker, index) => { const [lat, lon] = marker; + const pointId = marker[2]; const popupContent = createPopupContent(marker, userSettings.timezone, userSettings.distanceUnit); let markerColor = marker[5] < 0 ? "orange" : "blue"; - // Use L.marker instead of L.circleMarker for better drag support return L.marker([lat, lon], { icon: L.divIcon({ className: 'custom-div-icon', @@ -21,20 +21,179 @@ export function createMarkersArray(markersData, userSettings) { iconAnchor: [4, 4] }), draggable: true, - autoPan: true - }).bindPopup(popupContent, { autoClose: false }) + autoPan: true, + pointIndex: index, + pointId: pointId, + originalLat: lat, + originalLng: lon, + renderer: renderer + }).bindPopup(popupContent) .on('dragstart', function(e) { + console.log('Drag started', { index: this.options.pointIndex }); this.closePopup(); }) - .on('dragend', function(e) { + .on('drag', function(e) { const newLatLng = e.target.getLatLng(); + const map = e.target._map; + const pointIndex = e.target.options.pointIndex; + const originalLat = e.target.options.originalLat; + const originalLng = e.target.options.originalLng; + + console.log('Dragging point', { + pointIndex, + newPosition: newLatLng, + originalPosition: { lat: originalLat, lng: originalLng } + }); + + // Find polylines by iterating through all map layers + map.eachLayer((layer) => { + // Check if this is a LayerGroup containing polylines + if (layer instanceof L.LayerGroup) { + layer.eachLayer((featureGroup) => { + if (featureGroup instanceof L.FeatureGroup) { + featureGroup.eachLayer((segment) => { + if (segment instanceof L.Polyline) { + const coords = segment.getLatLngs(); + const tolerance = 0.0000001; + let updated = false; + + // Check and update start point + if (Math.abs(coords[0].lat - originalLat) < tolerance && + Math.abs(coords[0].lng - originalLng) < tolerance) { + console.log('Updating start point of segment', { + from: coords[0], + to: newLatLng + }); + coords[0] = newLatLng; + updated = true; + } + + // Check and update end point + if (Math.abs(coords[1].lat - originalLat) < tolerance && + Math.abs(coords[1].lng - originalLng) < tolerance) { + console.log('Updating end point of segment', { + from: coords[1], + to: newLatLng + }); + coords[1] = newLatLng; + updated = true; + } + + // Only update if we found a matching endpoint + if (updated) { + segment.setLatLngs(coords); + segment.redraw(); + } + } + }); + } + }); + } + }); + + // Update the marker's original position for the next drag event + e.target.options.originalLat = newLatLng.lat; + e.target.options.originalLng = newLatLng.lng; + }) + .on('dragend', function(e) { + console.log('Drag ended', { + finalPosition: e.target.getLatLng(), + pointIndex: e.target.options.pointIndex + }); + const newLatLng = e.target.getLatLng(); + const pointId = e.target.options.pointId; + const pointIndex = e.target.options.pointIndex; + + // Update the marker's position this.setLatLng(newLatLng); this.openPopup(); + + // Send API request to update point position + fetch(`/api/v1/points/${pointId}`, { + method: 'PATCH', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + 'Authorization': `Bearer ${apiKey}` + }, + body: JSON.stringify({ + point: { + latitude: newLatLng.lat, + longitude: newLatLng.lng + } + }) + }) + .then(response => { + if (!response.ok) { + throw new Error('Failed to update point position'); + } + return response.json(); + }) + .then(data => { + // Update the markers array in the controller + const map = e.target._map; + const mapsController = map.mapsController; + if (mapsController && mapsController.markers) { + mapsController.markers[pointIndex][0] = newLatLng.lat; + mapsController.markers[pointIndex][1] = newLatLng.lng; + + // Store current polylines visibility state + const wasPolyLayerVisible = map.hasLayer(mapsController.polylinesLayer); + + // Remove old polylines layer + if (mapsController.polylinesLayer) { + map.removeLayer(mapsController.polylinesLayer); + } + + // Create new polylines layer with updated coordinates + mapsController.polylinesLayer = createPolylinesLayer( + mapsController.markers, + map, + mapsController.timezone, + mapsController.routeOpacity, + mapsController.userSettings, + mapsController.distanceUnit + ); + + // Restore polylines visibility if it was visible before + if (wasPolyLayerVisible) { + mapsController.polylinesLayer.addTo(map); + } + } + + // Update popup content with new data + const updatedPopupContent = createPopupContent( + [ + data.latitude, + data.longitude, + data.id, + data.altitude, + data.timestamp, + data.velocity || 0 + ], + userSettings.timezone, + userSettings.distanceUnit + ); + this.setPopupContent(updatedPopupContent); + }) + .catch(error => { + console.error('Error updating point position:', error); + // Revert the marker position on error + this.setLatLng([lat, lon]); + }); }); }); } } +// Helper function to check if a point is connected to a polyline endpoint +function isConnectedToPoint(latLng, originalPoint, tolerance) { + // originalPoint is [lat, lng] array + const latMatch = Math.abs(latLng.lat - originalPoint[0]) < tolerance; + const lngMatch = Math.abs(latLng.lng - originalPoint[1]) < tolerance; + return latMatch && lngMatch; +} + export function createSimplifiedMarkers(markersData, renderer) { const distanceThreshold = 50; // meters const timeThreshold = 20000; // milliseconds (3 seconds) @@ -47,7 +206,6 @@ export function createSimplifiedMarkers(markersData, renderer) { if (index === 0) return; // Skip the first marker const [prevLat, prevLon, prevTimestamp] = previousMarker; - const [currLat, currLon, currTimestamp] = currentMarker; const timeDiff = currTimestamp - prevTimestamp; const distance = haversineDistance(prevLat, prevLon, currLat, currLon, 'km') * 1000; // Convert km to meters diff --git a/config/routes.rb b/config/routes.rb index 8d28efde..0befcca4 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -67,7 +67,7 @@ Rails.application.routes.draw do get 'settings', to: 'settings#index' resources :areas, only: %i[index create update destroy] - resources :points, only: %i[index destroy] + resources :points, only: %i[index destroy update] resources :visits, only: %i[update] resources :stats, only: :index