// Color scheme for countries
const countryColors = {
'South Africa': '#FF6B6B',
'Zimbabwe': '#4ECDC4',
'eSwatini': '#45B7D1',
'Mozambique': '#96CEB4',
'Malawi': '#FFEAA7',
'Zambia': '#DDA15E',
'Uganda': '#EE6C4D',
'Kenya': '#F1A208',
'Tanzania': '#A23B72',
'Rwanda': '#8E7DBE',
'Burundi': '#C9ADA7',
'Mexico': '#006846',
};
let map;
let mills = [];
let millMarkers = {};
let drawnItems = new Map();
let currentMode = 'view';
let currentEditingId = null;
let featureGroup;
let drawControl;
let osmLayer;
let satelliteLayer;
let currentMapType = 'osm';
let measureControl = null;
let filteredMills = [];
let currentFilters = {
search: '',
country: '',
minProduction: 0
};
let isMeasuring = false;
let measurePoints = [];
// Initialize map
function initMap() {
if (!document.getElementById('map')) {
console.error('Map container not found!');
return;
}
try {
if (typeof L === 'undefined') {
throw new Error('Leaflet library (L) is not defined!');
}
map = L.map('map').setView([-20, 33], 5);
} catch (e) {
console.error('Error initializing map:', e);
return;
}
// OpenStreetMap layer
osmLayer = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: 'Β© OpenStreetMap contributors',
maxZoom: 19
}).addTo(map);
// Satellite layer (Esri)
satelliteLayer = L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', {
attribution: 'Β© Esri',
maxZoom: 19
});
// Initialize drawing
featureGroup = L.featureGroup().addTo(map);
drawControl = new L.Control.Draw({
edit: {
featureGroup: featureGroup,
poly: {
allowIntersection: false
}
},
draw: {
polygon: false,
polyline: false,
rectangle: false,
circle: false,
circlemarker: false,
marker: true
}
});
map.addControl(drawControl);
// Hide draw controls initially (show only in draw mode)
const drawToolbar = document.querySelector('.leaflet-draw-toolbar');
if (drawToolbar) {
drawToolbar.style.display = 'none';
}
// Handle drawn items
map.on(L.Draw.Event.CREATED, function(e) {
const layer = e.layer;
featureGroup.addLayer(layer);
if (layer instanceof L.Marker) {
// Auto-switch to draw mode if not already
if (currentMode !== 'draw') {
document.querySelector('[data-mode="draw"]').click();
}
const id = Date.now();
const latlng = layer.getLatLng();
// Round coordinates to 4 decimal places (~11m accuracy)
const roundedLat = Math.round(latlng.lat * 10000) / 10000;
const roundedLng = Math.round(latlng.lng * 10000) / 10000;
drawnItems.set(id, {
type: 'marker',
lat: roundedLat,
lng: roundedLng,
data: {}
});
layer.drawId = id;
updateDrawnItemsList();
showEditForm(id);
}
});
map.on(L.Draw.Event.DELETED, function(e) {
e.layers.eachLayer(function(layer) {
drawnItems.delete(layer.drawId);
});
updateDrawnItemsList();
});
// Satellite toggle
document.getElementById('mapToggleBtn').addEventListener('click', () => {
const btn = document.getElementById('mapToggleBtn');
if (currentMapType === 'osm') {
map.removeLayer(osmLayer);
map.addLayer(satelliteLayer);
currentMapType = 'satellite';
btn.textContent = 'πΊοΈ Map';
} else {
map.removeLayer(satelliteLayer);
map.addLayer(osmLayer);
currentMapType = 'osm';
btn.textContent = 'π°οΈ Satellite';
}
});
// Measurement tool toggle
document.getElementById('measureBtn').addEventListener('click', () => {
const btn = document.getElementById('measureBtn');
const panel = document.getElementById('measurementPanel');
isMeasuring = !isMeasuring;
if (isMeasuring) {
measurePoints = [];
btn.textContent = 'π Click points to measure';
btn.style.background = 'rgba(255,255,255,0.3)';
btn.style.fontWeight = '600';
panel.style.display = 'flex';
panel.style.flex = '1';
updateMeasurementPanel();
} else {
btn.textContent = 'π Measure';
btn.style.background = 'rgba(255,255,255,0.1)';
btn.style.fontWeight = '400';
panel.style.display = 'none';
panel.style.flex = '';
// Clear measurement points from map
map.eachLayer(layer => {
if (layer.options && layer.options.className === 'measurement-point') {
map.removeLayer(layer);
}
});
}
});
// Reset measurement button
document.getElementById('resetMeasurementBtn').addEventListener('click', () => {
measurePoints = [];
// Clear measurement points from map
map.eachLayer(layer => {
if (layer.options && layer.options.className === 'measurement-point') {
map.removeLayer(layer);
}
});
updateMeasurementPanel();
});
// Add map click listener for measurement
map.on('click', function(e) {
if (!isMeasuring) return;
const point = e.latlng;
measurePoints.push(point);
// Add marker
L.circleMarker(point, {
radius: 5,
fillColor: '#2596be',
color: 'white',
weight: 2,
opacity: 1,
fillOpacity: 0.8,
className: 'measurement-point'
}).addTo(map);
// If we have 2 or more points, show distance
if (measurePoints.length >= 2) {
const lastPoint = measurePoints[measurePoints.length - 2];
const currentPoint = measurePoints[measurePoints.length - 1];
const distance = lastPoint.distanceTo(currentPoint);
// Add polyline between points
L.polyline([lastPoint, currentPoint], {
color: '#2596be',
weight: 2,
opacity: 0.7,
dashArray: '5, 5',
className: 'measurement-point'
}).addTo(map);
}
// Update the measurement panel
updateMeasurementPanel();
});
// Load CSV data
loadMillsData();
// Initialize Google Sheets auto-refresh
initGoogleSheetsAutoRefresh();
showGoogleSheetsSetup();
// Attach mode button listeners
attachModeListeners();
// Attach filter listeners
attachFilterListeners();
}
// Attach filter event listeners
function attachFilterListeners() {
document.getElementById('searchInput').addEventListener('input', applyFilters);
document.getElementById('countryFilter').addEventListener('change', applyFilters);
document.getElementById('minProduction').addEventListener('change', applyFilters);
document.getElementById('resetFiltersBtn').addEventListener('click', resetFilters);
}
// Update measurement panel with current points and distances
function updateMeasurementPanel() {
const listDiv = document.getElementById('measurementList');
const totalDiv = document.getElementById('totalDistance');
if (measurePoints.length === 0) {
listDiv.innerHTML = '
Click on the map to add measurement points
';
totalDiv.textContent = '0.00 km';
return;
}
let html = '';
let totalDistance = 0;
// Show each point
measurePoints.forEach((point, index) => {
let segmentDistance = '';
if (index > 0) {
const prevPoint = measurePoints[index - 1];
const dist = prevPoint.distanceTo(point);
const distKm = (dist / 1000).toFixed(2);
segmentDistance = ` - Segment: ${distKm} km`;
totalDistance += dist;
}
html += `
Point ${index + 1} (${point.lat.toFixed(4)}, ${point.lng.toFixed(4)})${segmentDistance}
`;
});
listDiv.innerHTML = html;
totalDiv.textContent = (totalDistance / 1000).toFixed(2) + ' km';
}
// Load mills from CSV
async function loadMillsData() {
try {
// Try to load from Google Sheets first
let csvText = await fetchGoogleSheetData();
// Fallback to local CSV if Google Sheets fails
if (!csvText) {
console.log('Falling back to local CSV file...');
const response = await fetch('sugar_cane_factories_africa.csv');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
csvText = await response.text();
showNotification('Using local data (Google Sheet unavailable)', 'warning');
} else {
showNotification('β Data loaded from Google Sheet', 'success');
}
parseCSV(csvText);
console.log('Mills loaded:', mills.length, mills.slice(0, 2));
renderMills();
console.log('Mills rendered');
updateLegend();
console.log('Legend updated');
} catch (error) {
console.error('Error loading mills data:', error);
const notification = document.createElement('div');
notification.style.cssText = 'position: fixed; top: 20px; right: 20px; background: #ff6b6b; color: white; padding: 15px 20px; border-radius: 5px; z-index: 9999;';
notification.textContent = 'β οΈ Could not load mill data. Check console for details.';
document.body.appendChild(notification);
setTimeout(() => notification.remove(), 5000);
}
}
// Parse CSV
function parseCSV(csvText) {
const lines = csvText.trim().split('\n');
const headers = lines[0].split(',').map(h => h.trim());
mills = lines.slice(1).map((line, index) => {
const values = parseCSVLine(line);
const row = {};
headers.forEach((header, idx) => {
row[header] = values[idx] ? values[idx].trim() : '';
});
row._id = index; // Internal unique ID
return row;
}).filter(row => row.Latitude && row.Longitude);
}
// Parse CSV line handling quoted values
function parseCSVLine(line) {
const result = [];
let current = '';
let inQuotes = false;
for (let i = 0; i < line.length; i++) {
const char = line[i];
const nextChar = line[i + 1];
if (char === '"') {
if (inQuotes && nextChar === '"') {
current += '"';
i++;
} else {
inQuotes = !inQuotes;
}
} else if (char === ',' && !inQuotes) {
result.push(current);
current = '';
} else {
current += char;
}
}
result.push(current);
return result;
}
// Render mills on map
function renderMills() {
Object.values(millMarkers).forEach(marker => {
if (map.hasLayer(marker)) map.removeLayer(marker);
});
millMarkers = {};
mills.forEach(mill => {
const lat = parseFloat(mill.Latitude);
const lng = parseFloat(mill.Longitude);
if (!isNaN(lat) && !isNaN(lng)) {
const production = cleanNumber(mill['Annual Sugar Production (tons)']);
const size = getCircleSize(production);
const color = countryColors[mill.Country] || '#999';
const marker = L.circleMarker([lat, lng], {
radius: size,
fillColor: color,
color: 'white',
weight: 2,
opacity: 0.8,
fillOpacity: 0.7
}).addTo(map);
const popup = createPopup(mill);
marker.bindPopup(popup);
millMarkers[mill._id] = marker;
}
});
// Initialize filtered mills array
filteredMills = [...mills];
}
// Get circle size based on production
function getCircleSize(production) {
if (production > 150000) return 15;
if (production > 50000) return 10;
return 6;
}
// Create popup content
function createPopup(mill) {
const production = mill['Annual Sugar Production (tons)'] || 'N/A';
const capacity = mill['Crushing Capacity (tons/year)'] || 'N/A';
const year = mill['Data Year'] || '';
return `
${mill['Mill/Factory']}
${mill['Country']} β’ ${mill['Province/Region']}
| Company: |
${mill.Company || 'N/A'} |
| Production: |
${formatNumber(production)} tons/year |
| Capacity: |
${formatNumber(capacity)} tons/year |
| Coordinates: |
${parseFloat(mill.Latitude).toFixed(4)}, ${parseFloat(mill.Longitude).toFixed(4)} |
| Data Year: |
${year} |
${mill.Notes ? `| "${mill.Notes}" |
` : ''}
`;
}
// Format numbers
function formatNumber(num) {
if (!num || num === 'N/A') return 'N/A';
const n = parseFloat(num);
if (isNaN(n)) return 'N/A';
return n.toLocaleString('en-US', { maximumFractionDigits: 0 });
}
// Update legend and populate country filter
function updateLegend() {
const countries = [...new Set(mills.map(m => m.Country))].sort();
const legendHTML = countries.map(country => {
const color = countryColors[country] || '#999';
const count = mills.filter(m => m.Country === country).length;
return `
`;
}).join('');
document.getElementById('legendContainer').innerHTML = legendHTML;
// Update country filter dropdown
const countryFilter = document.getElementById('countryFilter');
const currentValue = countryFilter.value;
countryFilter.innerHTML = '' +
countries.map(c => ``).join('');
countryFilter.value = currentValue;
}
// Helper to clean numbers (remove commas and spaces)
function cleanNumber(val) {
if (!val) return 0;
const cleanStr = String(val).replace(/[, ]/g, '');
return parseFloat(cleanStr) || 0;
}
// Apply filters to mills
function applyFilters() {
const search = document.getElementById('searchInput').value.toLowerCase();
const country = document.getElementById('countryFilter').value;
const minProduction = parseFloat(document.getElementById('minProduction').value) || 0;
console.log('Applying filters:', { search, country, minProduction, totalMills: mills.length });
currentFilters = { search, country, minProduction };
// Filter mills
filteredMills = mills.filter(mill => {
const matchesSearch = !search ||
mill['Mill/Factory'].toLowerCase().includes(search) ||
mill['Company'].toLowerCase().includes(search);
const matchesCountry = !country || mill.Country === country;
const production = cleanNumber(mill['Annual Sugar Production (tons)']);
const matchesProduction = production >= minProduction;
return matchesSearch && matchesCountry && matchesProduction;
});
console.log('Filtered mills:', filteredMills.length);
// Update map display
updateMillsVisibility();
// Update legend to show only filtered countries
updateLegendFiltered();
// Zoom to filtered results if any exist
if (filteredMills.length > 0) {
zoomToFilteredBounds();
}
}
// Update visibility of mill markers based on filters
function updateMillsVisibility() {
mills.forEach(mill => {
const marker = millMarkers[mill._id];
if (marker) {
const isVisible = filteredMills.includes(mill);
if (isVisible) {
if (!map.hasLayer(marker)) {
marker.addTo(map);
}
marker.setStyle({
opacity: 0.8,
fillOpacity: 0.7
});
} else {
if (map.hasLayer(marker)) {
map.removeLayer(marker);
}
}
}
});
}
// Zoom map to encapsulate all filtered results
function zoomToFilteredBounds() {
if (filteredMills.length === 0) return;
const bounds = L.latLngBounds();
let hasValidBounds = false;
filteredMills.forEach(mill => {
const lat = parseFloat(mill.Latitude);
const lng = parseFloat(mill.Longitude);
if (!isNaN(lat) && !isNaN(lng)) {
bounds.extend([lat, lng]);
hasValidBounds = true;
}
});
if (hasValidBounds) {
map.fitBounds(bounds, { padding: [50, 50], maxZoom: 10 });
}
}
// Update legend to show only countries in filtered results
function updateLegendFiltered() {
const filteredCountries = [...new Set(filteredMills.map(m => m.Country))].sort();
const legendHTML = filteredCountries.map(country => {
const color = countryColors[country] || '#999';
const count = filteredMills.filter(m => m.Country === country).length;
return `
`;
}).join('');
document.getElementById('legendContainer').innerHTML = legendHTML || 'No mills match current filters
';
}
// Reset all filters
function resetFilters() {
document.getElementById('searchInput').value = '';
document.getElementById('countryFilter').value = '';
document.getElementById('minProduction').value = '';
filteredMills = [...mills];
updateMillsVisibility();
updateLegend(); // Show all countries again
// Reset map to initial view
map.setView([-20, 33], 5);
}
// Mode switching
function attachModeListeners() {
const buttons = document.querySelectorAll('.mode-btn');
console.log(`Attaching listeners to ${buttons.length} mode buttons`);
buttons.forEach(btn => {
btn.addEventListener('click', (e) => {
console.log('Mode button clicked:', e.target.dataset.mode);
document.querySelectorAll('.mode-btn').forEach(b => b.classList.remove('active'));
e.target.classList.add('active');
currentMode = e.target.dataset.mode;
console.log('Current mode:', currentMode);
document.getElementById('viewModePanel').style.display = currentMode === 'view' ? 'block' : 'none';
document.getElementById('drawModePanel').style.display = currentMode === 'draw' ? 'block' : 'none';
// Show/hide draw toolbar based on mode
const drawToolbar = document.querySelector('.leaflet-draw-toolbar');
if (drawToolbar) {
drawToolbar.style.display = currentMode === 'draw' ? 'block' : 'none';
}
updateDrawnItemsList();
});
});
}
// Update drawn items list
function updateDrawnItemsList() {
const list = document.getElementById('drawnItemsList');
if (drawnItems.size === 0) {
list.innerHTML = 'No items drawn yet
';
document.getElementById('showFormBtn').classList.remove('visible');
document.getElementById('exportBtn').classList.remove('visible');
document.getElementById('clearDrawnBtn').classList.remove('visible');
} else {
const items = Array.from(drawnItems.entries()).map(([id, item]) => `
${item.data['Mill/Factory'] || `Point ${id.toString().slice(-4)}`}
`).join('');
list.innerHTML = items;
document.getElementById('showFormBtn').classList.add('visible');
document.getElementById('exportBtn').classList.add('visible');
document.getElementById('clearDrawnBtn').classList.add('visible');
}
}
// Remove drawn item
function removeDrawnItem(id) {
drawnItems.delete(id);
featureGroup.eachLayer(layer => {
if (layer.drawId === id) {
featureGroup.removeLayer(layer);
}
});
updateDrawnItemsList();
}
// Show edit form
function showEditForm(id) {
currentEditingId = id;
const item = drawnItems.get(id);
document.getElementById('editMill').value = item.data['Mill/Factory'] || '';
document.getElementById('editCountry').value = item.data['Country'] || '';
document.getElementById('editCompany').value = item.data['Company'] || '';
document.getElementById('editProvince').value = item.data['Province/Region'] || '';
document.getElementById('editLat').value = item.lat.toFixed(4);
document.getElementById('editLng').value = item.lng.toFixed(4);
document.getElementById('editCapacity').value = item.data['Crushing Capacity (tons/year)'] || '';
document.getElementById('editProduction').value = item.data['Annual Sugar Production (tons)'] || '';
document.getElementById('editNotes').value = item.data['Notes'] || '';
document.getElementById('editYear').value = item.data['Data Year'] || new Date().getFullYear();
document.getElementById('editAnnotations').value = item.data['Annotations'] || '';
document.getElementById('editModal').classList.add('active');
}
// Handle form submission
document.getElementById('editForm').addEventListener('submit', (e) => {
e.preventDefault();
const item = drawnItems.get(currentEditingId);
item.data = {
'Country': document.getElementById('editCountry').value,
'Mill/Factory': document.getElementById('editMill').value,
'Company': document.getElementById('editCompany').value,
'Province/Region': document.getElementById('editProvince').value,
'Latitude': parseFloat(document.getElementById('editLat').value),
'Longitude': parseFloat(document.getElementById('editLng').value),
'Crushing Capacity (tons/year)': document.getElementById('editCapacity').value,
'Annual Sugar Production (tons)': document.getElementById('editProduction').value,
'Notes': document.getElementById('editNotes').value,
'Data Year': document.getElementById('editYear').value,
'Annotations': document.getElementById('editAnnotations').value
};
drawnItems.set(currentEditingId, item);
updateDrawnItemsList();
closeModal('editModal');
});
// Modal controls
function closeModal(modalId) {
document.getElementById(modalId).classList.remove('active');
}
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') {
document.querySelectorAll('.modal').forEach(m => m.classList.remove('active'));
}
});
// Export to CSV - Option 1: Download locally
function exportToCSV() {
if (drawnItems.size === 0) {
alert('No items to export');
return;
}
// Header row
const headers = ['Country', 'Mill/Factory', 'Company', 'Province/Region', 'Latitude', 'Longitude', 'Crushing Capacity (tons/year)', 'Annual Sugar Production (tons)', 'Notes', 'Data Year', 'Annotations'];
const rows = [headers];
// Data rows
drawnItems.forEach(item => {
const row = [
item.data['Country'] || '',
item.data['Mill/Factory'] || '',
item.data['Company'] || '',
item.data['Province/Region'] || '',
item.lat,
item.lng,
item.data['Crushing Capacity (tons/year)'] || '',
item.data['Annual Sugar Production (tons)'] || '',
`"${(item.data['Notes'] || '').replace(/"/g, '""')}"`,
item.data['Data Year'] || '',
`"${(item.data['Annotations'] || '').replace(/"/g, '""')}"`
];
rows.push(row);
});
// Convert to CSV
const csv = rows.map(row =>
row.map(cell => {
if (typeof cell === 'string' && (cell.includes(',') || cell.includes('"') || cell.includes('\n'))) {
return `"${cell.replace(/"/g, '""')}"`;
}
return cell;
}).join(',')
).join('\n');
return csv;
}
// Export button - show options
document.getElementById('exportBtn').addEventListener('click', () => {
if (drawnItems.size === 0) {
alert('No items to export');
return;
}
showExportModal();
});
// Export modal
function showExportModal() {
const modal = document.createElement('div');
modal.className = 'modal active';
modal.id = 'exportModal';
modal.innerHTML = `
You have ${drawnItems.size} new mill location(s) to submit. Choose how to send them:
π€ Submit Online
Send all new mills directly to the database. Your data will be reviewed and added.
β¬οΈ Download as CSV
Download the data as a CSV file for your records or manual submission.
`;
document.body.appendChild(modal);
}
// Submit via Formspree
function submitViaFormspree() {
const csv = exportToCSV();
// Build form data
const formData = new FormData();
formData.append('submissionType', 'New Mill Submissions');
formData.append('millCount', drawnItems.size);
formData.append('csvData', csv);
// Add individual mill details to the message
let millDetails = 'New Mills Submitted:\n\n';
let idx = 1;
drawnItems.forEach((item) => {
millDetails += `${idx}. ${item.data['Mill/Factory'] || 'Unnamed'}\n`;
millDetails += ` Country: ${item.data['Country'] || 'N/A'}\n`;
millDetails += ` Company: ${item.data['Company'] || 'N/A'}\n`;
millDetails += ` Province/Region: ${item.data['Province/Region'] || 'N/A'}\n`;
millDetails += ` Location: ${item.lat.toFixed(4)}, ${item.lng.toFixed(4)}\n`;
millDetails += ` Production: ${item.data['Annual Sugar Production (tons)'] || 'N/A'} tons/year\n`;
millDetails += ` Capacity: ${item.data['Crushing Capacity (tons/year)'] || 'N/A'} tons/year\n`;
millDetails += ` Notes: ${item.data['Notes'] || 'None'}\n`;
millDetails += ` Annotations: ${item.data['Annotations'] || 'None'}\n\n`;
idx++;
});
formData.append('message', millDetails);
// Submit to Formspree
fetch('https://formspree.io/f/xgvgybwl', {
method: 'POST',
body: formData,
headers: {
'Accept': 'application/json'
}
})
.then(response => {
if (response.ok) {
alert(`β
Successfully submitted ${drawnItems.size} new mill(s)! Thank you for the contribution.`);
drawnItems.clear();
featureGroup.clearLayers();
updateDrawnItemsList();
closeModal('exportModal');
} else {
alert('β Error submitting mills. Please try again.');
}
})
.catch(error => {
console.error('Error:', error);
alert('β Error submitting mills. Please check your internet connection.');
});
}
// Download CSV
function downloadCSV() {
const csv = exportToCSV();
const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
const link = document.createElement('a');
const url = URL.createObjectURL(blob);
link.setAttribute('href', url);
link.setAttribute('download', `sugar_mills_additions_${new Date().toISOString().split('T')[0]}.csv`);
link.style.visibility = 'hidden';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
closeModal('exportModal');
}
// Email CSV via mailto
function emailCSV() {
const csv = exportToCSV();
const filename = `sugar_mills_additions_${new Date().toISOString().split('T')[0]}.csv`;
// Create summary for email body
let summary = 'New Sugar Cane Mill Locations\n\n';
summary += `Attached: ${filename}\n\n`;
summary += 'Summary of new mills:\n\n';
drawnItems.forEach((item, idx) => {
summary += `${idx + 1}. ${item.data['Mill/Factory'] || 'Unnamed'} (${item.data['Country'] || 'Unknown'})\n`;
summary += ` Company: ${item.data['Company'] || 'N/A'}\n`;
summary += ` Location: ${item.lat.toFixed(4)}, ${item.lng.toFixed(4)}\n`;
summary += ` Production: ${item.data['Annual Sugar Production (tons)'] || 'N/A'} tons/year\n\n`;
});
// First, download the CSV file
const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.setAttribute('href', url);
link.setAttribute('download', filename);
link.style.visibility = 'hidden';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
// Then open email client with instructions
const subject = encodeURIComponent(`New Sugar Cane Mills Data - ${new Date().toISOString().split('T')[0]}`);
const body = encodeURIComponent(summary);
const mailto = `mailto:timon@resiliencebv.com?subject=${subject}&body=${body}`;
// Show info that file was downloaded
setTimeout(() => {
alert(`β
CSV file downloaded: ${filename}\n\nPlease attach it to the email that will now open, then send it.`);
window.open(mailto);
}, 500);
closeModal('exportModal');
}
// Show form button
document.getElementById('showFormBtn').addEventListener('click', () => {
if (drawnItems.size > 0) {
const firstId = drawnItems.keys().next().value;
showEditForm(firstId);
}
});
// Clear all
document.getElementById('clearDrawnBtn').addEventListener('click', () => {
if (confirm('Clear all drawn items?')) {
drawnItems.clear();
featureGroup.clearLayers();
updateDrawnItemsList();
}
});
// Initialize on load
window.addEventListener('load', function() {
initMap();
});
// Also try immediate initialization if document is already loaded
if (document.readyState === 'complete' || document.readyState === 'interactive') {
setTimeout(function() {
initMap();
}, 500);
}