dawarich/app/javascript/controllers/public_stat_map_controller.js

302 lines
9.9 KiB
JavaScript
Raw Normal View History

2025-09-12 02:33:51 -04:00
import L from "leaflet";
import { createHexagonGrid } from "../maps/hexagon_grid";
import { createAllMapLayers } from "../maps/layers";
import BaseController from "./base_controller";
2025-09-12 02:33:51 -04:00
export default class extends BaseController {
2025-09-12 02:33:51 -04:00
static targets = ["container"];
2025-09-12 14:11:14 -04:00
static values = {
year: Number,
month: Number,
2025-09-12 02:33:51 -04:00
uuid: String,
dataBounds: Object,
2025-09-13 17:11:42 -04:00
hexagonsAvailable: Boolean,
selfHosted: String
2025-09-12 02:33:51 -04:00
};
connect() {
super.connect();
2025-09-12 14:11:14 -04:00
console.log('🏁 Controller connected - loading overlay should be visible');
this.selfHosted = this.selfHostedValue || 'false';
2025-09-12 02:33:51 -04:00
this.initializeMap();
this.loadHexagons();
}
disconnect() {
2025-09-14 06:41:16 -04:00
// No hexagonGrid to destroy for public sharing
2025-09-12 02:33:51 -04:00
if (this.map) {
this.map.remove();
}
}
initializeMap() {
// Initialize map with interactive controls enabled
this.map = L.map(this.element, {
zoomControl: true,
scrollWheelZoom: true,
doubleClickZoom: true,
touchZoom: true,
dragging: true,
keyboard: false
});
// Add dynamic tile layer based on self-hosted setting
this.addMapLayers();
// Default view
this.map.setView([40.0, -100.0], 4);
}
addMapLayers() {
try {
// Use appropriate default layer based on self-hosted mode
const selectedLayerName = this.selfHosted === "true" ? "OpenStreetMap" : "Light";
const maps = createAllMapLayers(this.map, selectedLayerName, this.selfHosted);
// If no layers were created, fall back to OSM
if (Object.keys(maps).length === 0) {
console.warn('No map layers available, falling back to OSM');
this.addFallbackOSMLayer();
}
} catch (error) {
console.error('Error creating map layers:', error);
console.log('Falling back to OSM tile layer');
this.addFallbackOSMLayer();
}
}
addFallbackOSMLayer() {
2025-09-12 02:33:51 -04:00
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© OpenStreetMap contributors',
maxZoom: 15
}).addTo(this.map);
}
async loadHexagons() {
2025-09-12 14:11:14 -04:00
console.log('🎯 loadHexagons started - checking overlay state');
const initialLoadingElement = document.getElementById('map-loading');
console.log('📊 Initial overlay display:', initialLoadingElement?.style.display || 'default');
2025-09-12 02:33:51 -04:00
2025-09-12 14:11:14 -04:00
try {
2025-09-12 02:33:51 -04:00
// Use server-provided data bounds
const dataBounds = this.dataBoundsValue;
2025-09-12 14:11:14 -04:00
2025-09-12 02:33:51 -04:00
if (dataBounds && dataBounds.point_count > 0) {
// Set map view to data bounds BEFORE creating hexagon grid
this.map.fitBounds([
[dataBounds.min_lat, dataBounds.min_lng],
[dataBounds.max_lat, dataBounds.max_lng]
], { padding: [20, 20] });
2025-09-12 14:11:14 -04:00
2025-09-12 02:33:51 -04:00
// Wait for the map to finish fitting bounds
2025-09-12 14:11:14 -04:00
console.log('⏳ About to wait for map moveend - overlay should still be visible');
2025-09-12 02:33:51 -04:00
await new Promise(resolve => {
this.map.once('moveend', resolve);
// Fallback timeout in case moveend doesn't fire
setTimeout(resolve, 1000);
});
2025-09-12 14:11:14 -04:00
console.log('✅ Map fitBounds complete - checking overlay state');
const afterFitBoundsElement = document.getElementById('map-loading');
console.log('📊 After fitBounds overlay display:', afterFitBoundsElement?.style.display || 'default');
2025-09-12 02:33:51 -04:00
}
2025-09-14 06:41:16 -04:00
// Don't create hexagonGrid for public sharing - we handle hexagons manually
// this.hexagonGrid = createHexagonGrid(this.map, {...});
console.log('🎯 Public sharing: skipping HexagonGrid creation, using manual loading');
console.log('🔍 Debug values:');
console.log(' dataBounds:', dataBounds);
console.log(' point_count:', dataBounds?.point_count);
console.log(' hexagonsAvailableValue:', this.hexagonsAvailableValue);
console.log(' hexagonsAvailableValue type:', typeof this.hexagonsAvailableValue);
2025-09-12 02:33:51 -04:00
2025-09-13 17:11:42 -04:00
// Load hexagons only if they are pre-calculated and data exists
if (dataBounds && dataBounds.point_count > 0 && this.hexagonsAvailableValue) {
2025-09-12 02:33:51 -04:00
await this.loadStaticHexagons();
} else {
2025-09-13 17:11:42 -04:00
if (!this.hexagonsAvailableValue) {
2025-09-14 06:41:16 -04:00
console.log('📋 No pre-calculated hexagons available for public sharing - skipping hexagon loading');
2025-09-13 17:11:42 -04:00
} else {
2025-09-14 06:41:16 -04:00
console.warn('⚠️ No data bounds or points available - not showing hexagons');
2025-09-13 17:11:42 -04:00
}
// Hide loading indicator if no hexagons to load
2025-09-12 14:11:14 -04:00
const loadingElement = document.getElementById('map-loading');
if (loadingElement) {
loadingElement.style.display = 'none';
}
2025-09-12 02:33:51 -04:00
}
} catch (error) {
console.error('Error initializing hexagon grid:', error);
2025-09-12 14:11:14 -04:00
// Hide loading indicator on initialization error
2025-09-12 02:33:51 -04:00
const loadingElement = document.getElementById('map-loading');
if (loadingElement) {
loadingElement.style.display = 'none';
}
}
2025-09-12 14:11:14 -04:00
// Do NOT hide loading overlay here - let loadStaticHexagons() handle it completely
2025-09-12 02:33:51 -04:00
}
async loadStaticHexagons() {
console.log('🔄 Loading static hexagons for public sharing...');
2025-09-12 14:11:14 -04:00
// Ensure loading overlay is visible and disable map interaction
const loadingElement = document.getElementById('map-loading');
console.log('🔍 Loading element found:', !!loadingElement);
if (loadingElement) {
loadingElement.style.display = 'flex';
loadingElement.style.visibility = 'visible';
loadingElement.style.zIndex = '9999';
console.log('👁️ Loading overlay ENSURED visible - should be visible now');
}
// Disable map interaction during loading
this.map.dragging.disable();
this.map.touchZoom.disable();
this.map.doubleClickZoom.disable();
this.map.scrollWheelZoom.disable();
this.map.boxZoom.disable();
this.map.keyboard.disable();
if (this.map.tap) this.map.tap.disable();
// Add delay to ensure loading overlay is visible
await new Promise(resolve => setTimeout(resolve, 500));
2025-09-12 02:33:51 -04:00
try {
// Calculate date range for the month
const startDate = new Date(this.yearValue, this.monthValue - 1, 1);
const endDate = new Date(this.yearValue, this.monthValue, 0, 23, 59, 59);
2025-09-12 14:11:14 -04:00
2025-09-12 02:33:51 -04:00
// Use the full data bounds for hexagon request (not current map viewport)
const dataBounds = this.dataBoundsValue;
2025-09-12 14:11:14 -04:00
2025-09-12 02:33:51 -04:00
const params = new URLSearchParams({
min_lon: dataBounds.min_lng,
min_lat: dataBounds.min_lat,
max_lon: dataBounds.max_lng,
max_lat: dataBounds.max_lat,
hex_size: 1000, // Fixed 1km hexagons
start_date: startDate.toISOString(),
end_date: endDate.toISOString(),
uuid: this.uuidValue
});
const url = `/api/v1/maps/hexagons?${params}`;
console.log('📍 Fetching static hexagons from:', url);
2025-09-12 14:11:14 -04:00
2025-09-12 02:33:51 -04:00
const response = await fetch(url, {
headers: {
'Content-Type': 'application/json'
}
});
if (!response.ok) {
const errorText = await response.text();
console.error('Hexagon API error:', response.status, response.statusText, errorText);
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const geojsonData = await response.json();
console.log(`✅ Loaded ${geojsonData.features?.length || 0} hexagons`);
// Add hexagons directly to map as a static layer
if (geojsonData.features && geojsonData.features.length > 0) {
this.addStaticHexagonsToMap(geojsonData);
}
} catch (error) {
console.error('Failed to load static hexagons:', error);
2025-09-12 14:11:14 -04:00
} finally {
// Re-enable map interaction after loading (success or failure)
this.map.dragging.enable();
this.map.touchZoom.enable();
this.map.doubleClickZoom.enable();
this.map.scrollWheelZoom.enable();
this.map.boxZoom.enable();
this.map.keyboard.enable();
if (this.map.tap) this.map.tap.enable();
// Hide loading overlay
const loadingElement = document.getElementById('map-loading');
if (loadingElement) {
loadingElement.style.display = 'none';
console.log('🚫 Loading overlay hidden - hexagons are fully loaded');
}
2025-09-12 02:33:51 -04:00
}
}
addStaticHexagonsToMap(geojsonData) {
// Calculate max point count for color scaling
const maxPoints = Math.max(...geojsonData.features.map(f => f.properties.point_count));
const staticHexagonLayer = L.geoJSON(geojsonData, {
2025-09-12 14:11:14 -04:00
style: (feature) => this.styleHexagon(),
2025-09-12 02:33:51 -04:00
onEachFeature: (feature, layer) => {
// Add popup with statistics
const props = feature.properties;
const popupContent = this.buildPopupContent(props);
layer.bindPopup(popupContent);
// Add hover effects
layer.on({
mouseover: (e) => this.onHexagonMouseOver(e),
mouseout: (e) => this.onHexagonMouseOut(e)
});
}
});
staticHexagonLayer.addTo(this.map);
}
2025-09-12 14:11:14 -04:00
styleHexagon() {
2025-09-12 02:33:51 -04:00
return {
2025-09-12 14:11:14 -04:00
fillColor: '#3388ff',
fillOpacity: 0.3,
color: '#3388ff',
2025-09-12 02:33:51 -04:00
weight: 1,
2025-09-12 14:11:14 -04:00
opacity: 0.3
2025-09-12 02:33:51 -04:00
};
}
buildPopupContent(props) {
const startDate = props.earliest_point ? new Date(props.earliest_point).toLocaleDateString() : 'N/A';
const endDate = props.latest_point ? new Date(props.latest_point).toLocaleDateString() : 'N/A';
return `
<div style="font-size: 12px; line-height: 1.4;">
<strong>Date Range:</strong><br>
<small>${startDate} - ${endDate}</small>
</div>
`;
}
onHexagonMouseOver(e) {
const layer = e.target;
// Store original style before changing
if (!layer._originalStyle) {
layer._originalStyle = {
fillOpacity: layer.options.fillOpacity,
weight: layer.options.weight,
opacity: layer.options.opacity
};
}
2025-09-12 14:11:14 -04:00
2025-09-12 02:33:51 -04:00
layer.setStyle({
fillOpacity: 0.8,
weight: 2,
opacity: 1.0
});
}
onHexagonMouseOut(e) {
const layer = e.target;
// Reset to stored original style
if (layer._originalStyle) {
layer.setStyle(layer._originalStyle);
}
}
2025-09-15 14:10:53 -04:00
2025-09-12 14:11:14 -04:00
}