mirror of
https://github.com/Freika/dawarich.git
synced 2026-05-07 04:59:23 -05:00
Add tracks to map
This commit is contained in:
@@ -11,7 +11,9 @@ import {
|
||||
updatePolylinesColors,
|
||||
colorFormatEncode,
|
||||
colorFormatDecode,
|
||||
colorStopsFallback
|
||||
colorStopsFallback,
|
||||
reestablishPolylineEventHandlers,
|
||||
managePaneVisibility
|
||||
} from "../maps/polylines";
|
||||
|
||||
import {
|
||||
@@ -205,6 +207,9 @@ export default class extends BaseController {
|
||||
// Add the toggle panel button
|
||||
this.addTogglePanelButton();
|
||||
|
||||
// Add routes/tracks selector
|
||||
this.addRoutesTracksSelector();
|
||||
|
||||
// Check if we should open the panel based on localStorage or URL params
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const isPanelOpen = localStorage.getItem('mapPanelOpen') === 'true';
|
||||
@@ -553,6 +558,33 @@ export default class extends BaseController {
|
||||
const selectedLayerName = event.name;
|
||||
this.updatePreferredBaseLayer(selectedLayerName);
|
||||
});
|
||||
|
||||
// Add event listeners for overlay layer changes to keep routes/tracks selector in sync
|
||||
this.map.on('overlayadd', (event) => {
|
||||
if (event.name === 'Routes') {
|
||||
this.handleRouteLayerToggle('routes');
|
||||
// Re-establish event handlers when routes are manually added
|
||||
if (event.layer === this.polylinesLayer) {
|
||||
reestablishPolylineEventHandlers(this.polylinesLayer, this.map, this.userSettings, this.distanceUnit);
|
||||
}
|
||||
} else if (event.name === 'Tracks') {
|
||||
this.handleRouteLayerToggle('tracks');
|
||||
}
|
||||
|
||||
// Manage pane visibility when layers are manually toggled
|
||||
this.updatePaneVisibilityAfterLayerChange();
|
||||
});
|
||||
|
||||
this.map.on('overlayremove', (event) => {
|
||||
if (event.name === 'Routes' || event.name === 'Tracks') {
|
||||
// Don't auto-switch when layers are manually turned off
|
||||
// Just update the radio button state to reflect current visibility
|
||||
this.updateRadioButtonState();
|
||||
|
||||
// Manage pane visibility when layers are manually toggled
|
||||
this.updatePaneVisibilityAfterLayerChange();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
updatePreferredBaseLayer(selectedLayerName) {
|
||||
@@ -1056,11 +1088,27 @@ export default class extends BaseController {
|
||||
const layer = controlsLayer[name];
|
||||
if (wasVisible && layer) {
|
||||
layer.addTo(this.map);
|
||||
// Re-establish event handlers for polylines layer when it's re-added
|
||||
if (name === 'Routes' && layer === this.polylinesLayer) {
|
||||
reestablishPolylineEventHandlers(this.polylinesLayer, this.map, this.userSettings, this.distanceUnit);
|
||||
}
|
||||
} else if (layer && this.map.hasLayer(layer)) {
|
||||
this.map.removeLayer(layer);
|
||||
}
|
||||
});
|
||||
|
||||
// Manage pane visibility based on which layers are visible
|
||||
const routesVisible = this.map.hasLayer(this.polylinesLayer);
|
||||
const tracksVisible = this.tracksLayer && this.map.hasLayer(this.tracksLayer);
|
||||
|
||||
if (routesVisible && !tracksVisible) {
|
||||
managePaneVisibility(this.map, 'routes');
|
||||
} else if (tracksVisible && !routesVisible) {
|
||||
managePaneVisibility(this.map, 'tracks');
|
||||
} else {
|
||||
managePaneVisibility(this.map, 'both');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error updating map settings:', error);
|
||||
console.error(error.stack);
|
||||
@@ -1154,6 +1202,166 @@ export default class extends BaseController {
|
||||
this.map.addControl(new TogglePanelControl({ position: 'topright' }));
|
||||
}
|
||||
|
||||
addRoutesTracksSelector() {
|
||||
// Store reference to the controller instance for use in the control
|
||||
const controller = this;
|
||||
|
||||
const RouteTracksControl = L.Control.extend({
|
||||
onAdd: function(map) {
|
||||
const container = L.DomUtil.create('div', 'routes-tracks-selector leaflet-bar');
|
||||
container.style.backgroundColor = 'white';
|
||||
container.style.padding = '8px';
|
||||
container.style.borderRadius = '4px';
|
||||
container.style.boxShadow = '0 1px 4px rgba(0,0,0,0.3)';
|
||||
container.style.fontSize = '12px';
|
||||
container.style.lineHeight = '1.2';
|
||||
|
||||
// Get saved preference or default to 'routes'
|
||||
const savedPreference = localStorage.getItem('mapRouteMode') || 'routes';
|
||||
|
||||
container.innerHTML = `
|
||||
<div style="margin-bottom: 4px; font-weight: bold; text-align: center;">Display</div>
|
||||
<div>
|
||||
<label style="display: block; margin-bottom: 4px; cursor: pointer;">
|
||||
<input type="radio" name="route-mode" value="routes" ${savedPreference === 'routes' ? 'checked' : ''} style="margin-right: 4px;">
|
||||
Routes
|
||||
</label>
|
||||
<label style="display: block; cursor: pointer;">
|
||||
<input type="radio" name="route-mode" value="tracks" ${savedPreference === 'tracks' ? 'checked' : ''} style="margin-right: 4px;">
|
||||
Tracks
|
||||
</label>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Disable map interactions when clicking the control
|
||||
L.DomEvent.disableClickPropagation(container);
|
||||
|
||||
// Add change event listeners
|
||||
const radioButtons = container.querySelectorAll('input[name="route-mode"]');
|
||||
radioButtons.forEach(radio => {
|
||||
L.DomEvent.on(radio, 'change', () => {
|
||||
if (radio.checked) {
|
||||
controller.switchRouteMode(radio.value);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return container;
|
||||
}
|
||||
});
|
||||
|
||||
// Add the control to the map
|
||||
this.map.addControl(new RouteTracksControl({ position: 'topleft' }));
|
||||
|
||||
// Apply initial state based on saved preference
|
||||
const savedPreference = localStorage.getItem('mapRouteMode') || 'routes';
|
||||
this.switchRouteMode(savedPreference, true);
|
||||
|
||||
// Set initial pane visibility
|
||||
this.updatePaneVisibilityAfterLayerChange();
|
||||
}
|
||||
|
||||
switchRouteMode(mode, isInitial = false) {
|
||||
// Save preference to localStorage
|
||||
localStorage.setItem('mapRouteMode', mode);
|
||||
|
||||
if (mode === 'routes') {
|
||||
// Hide tracks layer if it exists and is visible
|
||||
if (this.tracksLayer && this.map.hasLayer(this.tracksLayer)) {
|
||||
this.map.removeLayer(this.tracksLayer);
|
||||
}
|
||||
|
||||
// Show routes layer if it exists and is not visible
|
||||
if (this.polylinesLayer && !this.map.hasLayer(this.polylinesLayer)) {
|
||||
this.map.addLayer(this.polylinesLayer);
|
||||
// Re-establish event handlers after adding the layer back
|
||||
reestablishPolylineEventHandlers(this.polylinesLayer, this.map, this.userSettings, this.distanceUnit);
|
||||
} else if (this.polylinesLayer) {
|
||||
reestablishPolylineEventHandlers(this.polylinesLayer, this.map, this.userSettings, this.distanceUnit);
|
||||
}
|
||||
|
||||
// Manage pane visibility to fix z-index blocking
|
||||
managePaneVisibility(this.map, 'routes');
|
||||
|
||||
// Update layer control checkboxes
|
||||
this.updateLayerControlCheckboxes('Routes', true);
|
||||
this.updateLayerControlCheckboxes('Tracks', false);
|
||||
} else if (mode === 'tracks') {
|
||||
// Hide routes layer if it exists and is visible
|
||||
if (this.polylinesLayer && this.map.hasLayer(this.polylinesLayer)) {
|
||||
this.map.removeLayer(this.polylinesLayer);
|
||||
}
|
||||
|
||||
// Show tracks layer if it exists and is not visible
|
||||
if (this.tracksLayer && !this.map.hasLayer(this.tracksLayer)) {
|
||||
this.map.addLayer(this.tracksLayer);
|
||||
}
|
||||
|
||||
// Manage pane visibility to fix z-index blocking
|
||||
managePaneVisibility(this.map, 'tracks');
|
||||
|
||||
// Update layer control checkboxes
|
||||
this.updateLayerControlCheckboxes('Routes', false);
|
||||
this.updateLayerControlCheckboxes('Tracks', true);
|
||||
}
|
||||
}
|
||||
|
||||
updateLayerControlCheckboxes(layerName, isVisible) {
|
||||
// Find the layer control input for the specified layer
|
||||
const layerControlContainer = document.querySelector('.leaflet-control-layers');
|
||||
if (!layerControlContainer) return;
|
||||
|
||||
const inputs = layerControlContainer.querySelectorAll('input[type="checkbox"]');
|
||||
inputs.forEach(input => {
|
||||
const label = input.nextElementSibling;
|
||||
if (label && label.textContent.trim() === layerName) {
|
||||
input.checked = isVisible;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
handleRouteLayerToggle(mode) {
|
||||
// Update the radio button selection
|
||||
const radioButtons = document.querySelectorAll('input[name="route-mode"]');
|
||||
radioButtons.forEach(radio => {
|
||||
if (radio.value === mode) {
|
||||
radio.checked = true;
|
||||
}
|
||||
});
|
||||
|
||||
// Switch to the selected mode and enforce mutual exclusivity
|
||||
this.switchRouteMode(mode);
|
||||
}
|
||||
|
||||
updateRadioButtonState() {
|
||||
// Update radio buttons to reflect current layer visibility
|
||||
const routesVisible = this.polylinesLayer && this.map.hasLayer(this.polylinesLayer);
|
||||
const tracksVisible = this.tracksLayer && this.map.hasLayer(this.tracksLayer);
|
||||
|
||||
const radioButtons = document.querySelectorAll('input[name="route-mode"]');
|
||||
radioButtons.forEach(radio => {
|
||||
if (radio.value === 'routes' && routesVisible && !tracksVisible) {
|
||||
radio.checked = true;
|
||||
} else if (radio.value === 'tracks' && tracksVisible && !routesVisible) {
|
||||
radio.checked = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
updatePaneVisibilityAfterLayerChange() {
|
||||
// Update pane visibility based on current layer visibility
|
||||
const routesVisible = this.polylinesLayer && this.map.hasLayer(this.polylinesLayer);
|
||||
const tracksVisible = this.tracksLayer && this.map.hasLayer(this.tracksLayer);
|
||||
|
||||
if (routesVisible && !tracksVisible) {
|
||||
managePaneVisibility(this.map, 'routes');
|
||||
} else if (tracksVisible && !routesVisible) {
|
||||
managePaneVisibility(this.map, 'tracks');
|
||||
} else {
|
||||
managePaneVisibility(this.map, 'both');
|
||||
}
|
||||
}
|
||||
|
||||
toggleRightPanel() {
|
||||
if (this.rightPanel) {
|
||||
const panel = document.querySelector('.leaflet-right-panel');
|
||||
@@ -1632,21 +1840,12 @@ export default class extends BaseController {
|
||||
|
||||
// Track-related methods
|
||||
async initializeTracksLayer() {
|
||||
console.log('DEBUG: Initializing tracks layer');
|
||||
console.log('DEBUG: this.tracksData:', this.tracksData);
|
||||
console.log('DEBUG: tracksData type:', typeof this.tracksData);
|
||||
console.log('DEBUG: tracksData length:', this.tracksData ? this.tracksData.length : 'undefined');
|
||||
|
||||
// Use pre-loaded tracks data if available, otherwise fetch from API
|
||||
if (this.tracksData && this.tracksData.length > 0) {
|
||||
console.log('DEBUG: Using pre-loaded tracks data');
|
||||
this.createTracksFromData(this.tracksData);
|
||||
} else {
|
||||
console.log('DEBUG: No pre-loaded tracks data, fetching from API');
|
||||
await this.fetchTracks();
|
||||
}
|
||||
|
||||
console.log('DEBUG: Tracks layer after initialization:', this.tracksLayer);
|
||||
}
|
||||
|
||||
async fetchTracks() {
|
||||
@@ -1683,14 +1882,7 @@ export default class extends BaseController {
|
||||
// Clear existing tracks
|
||||
this.tracksLayer.clearLayers();
|
||||
|
||||
console.log('DEBUG: Creating tracks from data:', {
|
||||
tracksData: tracksData,
|
||||
tracksCount: tracksData ? tracksData.length : 0,
|
||||
firstTrack: tracksData && tracksData.length > 0 ? tracksData[0] : null
|
||||
});
|
||||
|
||||
if (!tracksData || tracksData.length === 0) {
|
||||
console.log('DEBUG: No tracks data available');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1702,14 +1894,10 @@ export default class extends BaseController {
|
||||
this.distanceUnit
|
||||
);
|
||||
|
||||
console.log('DEBUG: Created tracks layer:', newTracksLayer);
|
||||
|
||||
// Add all tracks to the existing tracks layer
|
||||
newTracksLayer.eachLayer((layer) => {
|
||||
this.tracksLayer.addLayer(layer);
|
||||
});
|
||||
|
||||
console.log('DEBUG: Final tracks layer with', Object.keys(this.tracksLayer._layers).length, 'layers');
|
||||
}
|
||||
|
||||
updateLayerControl() {
|
||||
|
||||
@@ -464,6 +464,9 @@ export function createPolylinesLayer(markers, map, timezone, routeOpacity, userS
|
||||
segmentGroup.options.interactive = true;
|
||||
segmentGroup.options.bubblingMouseEvents = false;
|
||||
|
||||
// Store the original coordinates for later use
|
||||
segmentGroup._polylineCoordinates = polylineCoordinates;
|
||||
|
||||
// Add the hover functionality to the group
|
||||
addHighlightOnHover(segmentGroup, map, polylineCoordinates, userSettings, distanceUnit);
|
||||
|
||||
@@ -550,3 +553,120 @@ export function updatePolylinesOpacity(polylinesLayer, opacity) {
|
||||
segment.setStyle({ opacity: opacity });
|
||||
});
|
||||
}
|
||||
|
||||
export function reestablishPolylineEventHandlers(polylinesLayer, map, userSettings, distanceUnit) {
|
||||
let groupsProcessed = 0;
|
||||
let segmentsProcessed = 0;
|
||||
|
||||
// Re-establish event handlers for all polyline groups
|
||||
polylinesLayer.eachLayer((groupLayer) => {
|
||||
if (groupLayer instanceof L.LayerGroup || groupLayer instanceof L.FeatureGroup) {
|
||||
groupsProcessed++;
|
||||
|
||||
let segments = [];
|
||||
|
||||
groupLayer.eachLayer((segment) => {
|
||||
if (segment instanceof L.Polyline) {
|
||||
segments.push(segment);
|
||||
segmentsProcessed++;
|
||||
}
|
||||
});
|
||||
|
||||
// If we have stored polyline coordinates, use them; otherwise create a basic representation
|
||||
let polylineCoordinates = groupLayer._polylineCoordinates || [];
|
||||
|
||||
if (polylineCoordinates.length === 0) {
|
||||
// Fallback: reconstruct coordinates from segments
|
||||
const coordsMap = new Map();
|
||||
segments.forEach(segment => {
|
||||
const coords = segment.getLatLngs();
|
||||
coords.forEach(coord => {
|
||||
const key = `${coord.lat.toFixed(6)},${coord.lng.toFixed(6)}`;
|
||||
if (!coordsMap.has(key)) {
|
||||
const timestamp = segment.options.timestamp || Date.now() / 1000;
|
||||
const speed = segment.options.speed || 0;
|
||||
coordsMap.set(key, [coord.lat, coord.lng, 0, 0, timestamp, speed]);
|
||||
}
|
||||
});
|
||||
});
|
||||
polylineCoordinates = Array.from(coordsMap.values());
|
||||
}
|
||||
|
||||
// Re-establish the highlight hover functionality
|
||||
if (polylineCoordinates.length > 0) {
|
||||
addHighlightOnHover(groupLayer, map, polylineCoordinates, userSettings, distanceUnit);
|
||||
}
|
||||
|
||||
// Re-establish basic group event handlers
|
||||
groupLayer.on('mouseover', function(e) {
|
||||
L.DomEvent.stopPropagation(e);
|
||||
segments.forEach(segment => {
|
||||
segment.setStyle({
|
||||
weight: 8,
|
||||
opacity: 1
|
||||
});
|
||||
if (map.hasLayer(segment)) {
|
||||
segment.bringToFront();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
groupLayer.on('mouseout', function(e) {
|
||||
L.DomEvent.stopPropagation(e);
|
||||
segments.forEach(segment => {
|
||||
segment.setStyle({
|
||||
weight: 3,
|
||||
opacity: userSettings.route_opacity,
|
||||
color: segment.options.originalColor
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
groupLayer.on('click', function(e) {
|
||||
// Click handler placeholder
|
||||
});
|
||||
|
||||
// Ensure the group is interactive
|
||||
groupLayer.options.interactive = true;
|
||||
groupLayer.options.bubblingMouseEvents = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
export function managePaneVisibility(map, activeLayerType) {
|
||||
const polylinesPane = map.getPane('polylinesPane');
|
||||
const tracksPane = map.getPane('tracksPane');
|
||||
|
||||
if (activeLayerType === 'routes') {
|
||||
// Enable polylines pane events and disable tracks pane events
|
||||
if (polylinesPane) {
|
||||
polylinesPane.style.pointerEvents = 'auto';
|
||||
polylinesPane.style.zIndex = 470; // Temporarily boost above tracks
|
||||
}
|
||||
if (tracksPane) {
|
||||
tracksPane.style.pointerEvents = 'none';
|
||||
}
|
||||
} else if (activeLayerType === 'tracks') {
|
||||
// Enable tracks pane events and disable polylines pane events
|
||||
if (tracksPane) {
|
||||
tracksPane.style.pointerEvents = 'auto';
|
||||
tracksPane.style.zIndex = 470; // Boost above polylines
|
||||
}
|
||||
if (polylinesPane) {
|
||||
polylinesPane.style.pointerEvents = 'none';
|
||||
polylinesPane.style.zIndex = 450; // Reset to original
|
||||
}
|
||||
} else {
|
||||
// Both layers might be active or neither - enable both
|
||||
if (polylinesPane) {
|
||||
polylinesPane.style.pointerEvents = 'auto';
|
||||
polylinesPane.style.zIndex = 450; // Reset to original
|
||||
}
|
||||
if (tracksPane) {
|
||||
tracksPane.style.pointerEvents = 'auto';
|
||||
tracksPane.style.zIndex = 460; // Reset to original
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,7 +68,9 @@ export function addTrackInteractions(trackGroup, map, track, userSettings, dista
|
||||
const endMarker = L.marker([endCoord[0], endCoord[1]], { icon: endIcon });
|
||||
|
||||
function handleTrackHover(e) {
|
||||
if (isClicked) return; // Don't change hover state if clicked
|
||||
if (isClicked) {
|
||||
return; // Don't change hover state if clicked
|
||||
}
|
||||
|
||||
// Apply hover style to all segments in the track
|
||||
trackGroup.eachLayer((layer) => {
|
||||
@@ -185,36 +187,22 @@ export function addTrackInteractions(trackGroup, map, track, userSettings, dista
|
||||
}
|
||||
|
||||
function getTrackCoordinates(track) {
|
||||
// Add debugging to see what we're working with
|
||||
console.log(`DEBUG: Parsing track ${track.id}:`, {
|
||||
has_coordinates: !!(track.coordinates && Array.isArray(track.coordinates)),
|
||||
has_path: !!(track.path && Array.isArray(track.path)),
|
||||
original_path_type: typeof track.original_path,
|
||||
original_path_length: track.original_path ? track.original_path.length : 0,
|
||||
original_path_sample: track.original_path ? track.original_path.substring(0, 100) + '...' : null
|
||||
});
|
||||
|
||||
// First check if coordinates are already provided as an array
|
||||
if (track.coordinates && Array.isArray(track.coordinates)) {
|
||||
console.log(`DEBUG: Using coordinates array for track ${track.id}`);
|
||||
return track.coordinates; // If already provided as array of [lat, lng]
|
||||
}
|
||||
|
||||
// If coordinates are provided as a path property
|
||||
if (track.path && Array.isArray(track.path)) {
|
||||
console.log(`DEBUG: Using path array for track ${track.id}`);
|
||||
return track.path;
|
||||
}
|
||||
|
||||
// Try to parse from original_path (PostGIS LineString format)
|
||||
if (track.original_path && typeof track.original_path === 'string') {
|
||||
try {
|
||||
console.log(`DEBUG: Attempting to parse original_path for track ${track.id}: "${track.original_path}"`);
|
||||
|
||||
// Parse PostGIS LineString format: "LINESTRING (lng lat, lng lat, ...)" or "LINESTRING(lng lat, lng lat, ...)"
|
||||
const match = track.original_path.match(/LINESTRING\s*\(([^)]+)\)/i);
|
||||
if (match) {
|
||||
console.log(`DEBUG: LineString match found for track ${track.id}: "${match[1]}"`);
|
||||
const coordString = match[1];
|
||||
const coordinates = coordString.split(',').map(pair => {
|
||||
const [lng, lat] = pair.trim().split(/\s+/).map(parseFloat);
|
||||
@@ -225,8 +213,6 @@ function getTrackCoordinates(track) {
|
||||
return [lat, lng]; // Return as [lat, lng] for Leaflet
|
||||
}).filter(Boolean); // Remove null entries
|
||||
|
||||
console.log(`DEBUG: Parsed ${coordinates.length} coordinates for track ${track.id}`);
|
||||
|
||||
if (coordinates.length >= 2) {
|
||||
return coordinates;
|
||||
} else {
|
||||
@@ -243,7 +229,6 @@ function getTrackCoordinates(track) {
|
||||
|
||||
// For development/testing, create a simple line if we have start/end coordinates
|
||||
if (track.start_point && track.end_point) {
|
||||
console.log(`DEBUG: Using start/end points for track ${track.id}`);
|
||||
return [
|
||||
[track.start_point.lat, track.start_point.lng],
|
||||
[track.end_point.lat, track.end_point.lng]
|
||||
|
||||
Reference in New Issue
Block a user