Implement live map updates with WebSockets

This commit is contained in:
Eugene Burmakin 2024-11-03 16:48:43 +01:00
parent bec9db1198
commit 9c99a835de
9 changed files with 137 additions and 6 deletions

View file

@ -0,0 +1,7 @@
# frozen_string_literal: true
class PointsChannel < ApplicationCable::Channel
def subscribed
stream_for current_user
end
end

View file

@ -1,2 +1,3 @@
// Import all the channels to be used by Action Cable
import "notifications_channel"
import "points_channel"

View file

@ -0,0 +1,15 @@
import consumer from "./consumer"
consumer.subscriptions.create("PointsChannel", {
connected() {
// Called when the subscription is ready for use on the server
},
disconnected() {
// Called when the subscription has been terminated by the server
},
received(data) {
// Called when there's incoming data on the websocket for this channel
}
});

View file

@ -1,6 +1,7 @@
import { Controller } from "@hotwired/stimulus";
import L from "leaflet";
import "leaflet.heat";
import consumer from "../channels/consumer"; // Add this import
import { createMarkersArray } from "../maps/markers";
@ -138,12 +139,92 @@ export default class extends Controller {
this.map.removeControl(this.drawControl);
}
});
this.setupSubscription(); // Add this line
}
disconnect() {
this.map.remove();
}
setupSubscription() {
consumer.subscriptions.create("PointsChannel", {
received: (data) => {
this.appendPoint(data);
}
});
}
appendPoint(data) {
// Parse the received point data
console.log(data)
const newPoint = data;
// Add the new point to the markers array
this.markers.push(newPoint);
// Create a new marker for the point
const markerOptions = {
...this.userSettings, // Pass any relevant settings
id: newPoint[6], // Assuming index 6 contains the point ID
timestamp: newPoint[4] // Assuming index 2 contains the timestamp
};
const newMarker = this.createMarker(newPoint, markerOptions);
this.markersArray.push(newMarker);
// Update the markers layer
this.markersLayer.clearLayers();
this.markersLayer.addLayer(L.layerGroup(this.markersArray));
// Update heatmap
this.heatmapMarkers.push([newPoint[0], newPoint[1], 0.2]);
this.heatmapLayer.setLatLngs(this.heatmapMarkers);
// Update polylines
this.polylinesLayer.clearLayers();
this.polylinesLayer = createPolylinesLayer(
this.markers,
this.map,
this.timezone,
this.routeOpacity,
this.userSettings
);
// Pan map to new location
this.map.setView([newPoint[0], newPoint[1]], 14);
// Update fog of war if enabled
if (this.map.hasLayer(this.fogOverlay)) {
this.updateFog(this.markers, this.clearFogRadius);
}
// Update the last marker
this.map.eachLayer((layer) => {
if (layer instanceof L.Marker && !layer._popup) {
this.map.removeLayer(layer);
}
});
this.addLastMarker(this.map, this.markers);
}
createMarker(point, options) {
const marker = L.marker([point[0], point[1]]);
// Add popup content based on point data
const popupContent = `
<div>
<p>Time: ${new Date(point[2]).toLocaleString()}</p>
${point[3] ? `<p>Address: ${point[3]}</p>` : ''}
${point[7] ? `<p>Country: ${point[7]}</p>` : ''}
<a href="#" class="delete-point" data-id="${point[6]}">Delete</a>
</div>
`;
marker.bindPopup(popupContent);
return marker;
}
async setupScratchLayer(countryCodesMap) {
this.scratchLayer = L.geoJSON(null, {
style: {

View file

@ -21,11 +21,11 @@ class Notification < ApplicationRecord
NotificationsChannel.broadcast_to(
user,
{
id: id,
title: title,
content: content,
kind: kind,
timestamp: Time.current.to_i
# id: id,
# title: title,
# content: content,
# kind: kind,
# timestamp: Time.current.to_i
}
)
end

View file

@ -23,6 +23,7 @@ class Point < ApplicationRecord
scope :not_visited, -> { where(visit_id: nil) }
after_create :async_reverse_geocode
after_create_commit :broadcast_coordinates
def self.without_raw_data
select(column_names - ['raw_data'])
@ -37,4 +38,22 @@ class Point < ApplicationRecord
ReverseGeocodingJob.perform_later(self.class.to_s, id)
end
private
def broadcast_coordinates
PointsChannel.broadcast_to(
user,
[
latitude.to_f,
longitude.to_f,
battery.to_s,
altitude.to_s,
timestamp.to_s,
velocity.to_s,
id.to_s,
country.to_s
]
)
end
end

View file

@ -41,8 +41,10 @@
<% end %>
<div
id='map'
class="w-full"
data-controller="maps"
data-controller="maps points"
data-points-target="map"
data-distance_unit="<%= DISTANCE_UNIT %>"
data-api_key="<%= current_user.api_key %>"
data-user_settings=<%= current_user.settings.to_json %>

View file

@ -20,3 +20,4 @@ pin 'leaflet-draw' # @1.0.4
pin '@rails/actioncable', to: 'actioncable.esm.js'
pin_all_from 'app/javascript/channels', under: 'channels'
pin 'notifications_channel', to: 'channels/notifications_channel.js'
pin 'points_channel', to: 'channels/points_channel.js'

View file

@ -0,0 +1,5 @@
require 'rails_helper'
RSpec.describe PointsChannel, type: :channel do
pending "add some examples to (or delete) #{__FILE__}"
end