dawarich/app/javascript/controllers/trips_controller.js

230 lines
6.9 KiB
JavaScript

import { Controller } from "@hotwired/stimulus"
import L from "leaflet"
import {
osmMapLayer,
osmHotMapLayer,
OPNVMapLayer,
openTopoMapLayer,
cyclOsmMapLayer,
esriWorldStreetMapLayer,
esriWorldTopoMapLayer,
esriWorldImageryMapLayer,
esriWorldGrayCanvasMapLayer
} from "../maps/layers"
import { createPopupContent } from "../maps/popups"
import {
fetchAndDisplayPhotos,
showFlashMessage
} from '../maps/helpers';
export default class extends Controller {
static targets = ["container", "startedAt", "endedAt"]
static values = { }
connect() {
if (!this.hasContainerTarget) {
return;
}
console.log("Trips controller connected")
this.apiKey = this.containerTarget.dataset.api_key
this.userSettings = JSON.parse(this.containerTarget.dataset.user_settings || '{}')
this.timezone = this.containerTarget.dataset.timezone
this.distanceUnit = this.containerTarget.dataset.distance_unit
// Initialize map and layers
this.initializeMap()
// Add event listener for coordinates updates
this.element.addEventListener('coordinates-updated', (event) => {
console.log("Coordinates updated:", event.detail.coordinates)
this.updateMapWithCoordinates(event.detail.coordinates)
})
}
// Move map initialization to separate method
initializeMap() {
// Initialize layer groups
this.polylinesLayer = L.layerGroup()
this.photoMarkers = L.layerGroup()
// Set default center and zoom for world view
const center = [20, 0] // Roughly centers the world map
const zoom = 2
// Initialize map
this.map = L.map(this.containerTarget).setView(center, zoom)
// Add base map layer
osmMapLayer(this.map, "OpenStreetMap")
// Add scale control to bottom right
L.control.scale({
position: 'bottomright',
imperial: this.distanceUnit === 'mi',
metric: this.distanceUnit === 'km',
maxWidth: 120
}).addTo(this.map)
const overlayMaps = {
"Route": this.polylinesLayer,
"Photos": this.photoMarkers
}
// Add layer control
L.control.layers(this.baseMaps(), overlayMaps).addTo(this.map)
// Add event listener for layer changes
this.map.on('overlayadd', (e) => {
if (e.name !== 'Photos') return;
if ((!this.userSettings.immich_url || !this.userSettings.immich_api_key) && (!this.userSettings.photoprism_url || !this.userSettings.photoprism_api_key)) {
showFlashMessage(
'error',
'Photos integration is not configured. Please check your integrations settings.'
);
return;
}
if (!this.coordinates?.length) return;
const firstCoord = this.coordinates[0];
const lastCoord = this.coordinates[this.coordinates.length - 1];
const startDate = new Date(firstCoord[4] * 1000).toISOString().split('T')[0];
const endDate = new Date(lastCoord[4] * 1000).toISOString().split('T')[0];
fetchAndDisplayPhotos({
map: this.map,
photoMarkers: this.photoMarkers,
apiKey: this.apiKey,
startDate: startDate,
endDate: endDate,
userSettings: this.userSettings
});
});
// Add markers and route
if (this.coordinates?.length > 0) {
this.addMarkers()
this.addPolyline()
this.fitMapToBounds()
}
// After map initialization, add the path if it exists
if (this.containerTarget.dataset.path) {
const pathData = this.containerTarget.dataset.path.replace(/^"|"$/g, ''); // Remove surrounding quotes
const coordinates = this.parseLineString(pathData);
const polyline = L.polyline(coordinates, {
color: 'blue',
opacity: 0.8,
weight: 3,
zIndexOffset: 400
});
polyline.addTo(this.polylinesLayer);
this.polylinesLayer.addTo(this.map);
// Fit the map to the polyline bounds
if (coordinates.length > 0) {
this.map.fitBounds(polyline.getBounds(), { padding: [50, 50] });
}
}
}
disconnect() {
if (this.map) {
this.map.remove()
}
}
baseMaps() {
let selectedLayerName = this.userSettings.preferred_map_layer || "OpenStreetMap";
return {
OpenStreetMap: osmMapLayer(this.map, selectedLayerName),
"OpenStreetMap.HOT": osmHotMapLayer(this.map, selectedLayerName),
OPNV: OPNVMapLayer(this.map, selectedLayerName),
openTopo: openTopoMapLayer(this.map, selectedLayerName),
cyclOsm: cyclOsmMapLayer(this.map, selectedLayerName),
esriWorldStreet: esriWorldStreetMapLayer(this.map, selectedLayerName),
esriWorldTopo: esriWorldTopoMapLayer(this.map, selectedLayerName),
esriWorldImagery: esriWorldImageryMapLayer(this.map, selectedLayerName),
esriWorldGrayCanvas: esriWorldGrayCanvasMapLayer(this.map, selectedLayerName)
};
}
addMarkers() {
this.coordinates.forEach(coord => {
const marker = L.circleMarker(
[coord[0], coord[1]],
{
radius: 4,
color: coord[5] < 0 ? "orange" : "blue",
zIndexOffset: 1000
}
)
const popupContent = createPopupContent(coord, this.timezone, this.distanceUnit)
marker.bindPopup(popupContent)
})
}
addPolyline() {
const points = this.coordinates.map(coord => [coord[0], coord[1]])
const polyline = L.polyline(points, {
color: 'blue',
opacity: 0.8,
weight: 3,
zIndexOffset: 400
})
// Add to polylines layer instead of directly to map
this.polylinesLayer.addTo(this.map)
polyline.addTo(this.polylinesLayer)
}
fitMapToBounds() {
const bounds = L.latLngBounds(
this.coordinates.map(coord => [coord[0], coord[1]])
)
this.map.fitBounds(bounds, { padding: [50, 50] })
}
// Add this new method to update coordinates and refresh the map
updateMapWithCoordinates(newCoordinates) {
// Transform the coordinates to match the expected format
this.coordinates = newCoordinates.map(point => [
parseFloat(point.latitude),
parseFloat(point.longitude),
point.id,
null, // This is so we can use the same order and position of elements in the coordinates object as in the api/v1/points response
(point.timestamp).toString()
]).sort((a, b) => a[4] - b[4]);
// Clear existing layers
this.polylinesLayer.clearLayers()
this.photoMarkers.clearLayers()
// Add new markers and route if coordinates exist
if (this.coordinates?.length > 0) {
this.addMarkers()
this.addPolyline()
this.fitMapToBounds()
}
}
// Add this method to parse the LineString format
parseLineString(lineString) {
// Remove LINESTRING and parentheses, then split into coordinate pairs
const coordsString = lineString.replace('LINESTRING (', '').replace(')', '');
const coords = coordsString.split(', ');
// Convert each coordinate pair to [lat, lng] format
return coords.map(coord => {
const [lng, lat] = coord.split(' ').map(Number);
return [lat, lng]; // Swap to lat, lng for Leaflet
});
}
}