/** * Map Handler for EcoBuddy * Handles map initialization, postcode validation, and facility display */ // initialise map variables let map = null; let markers = []; let circle = null; let facilities = []; let currentPostcode = null; let currentRadius = 10; // Default radius in miles // initialise map on document load document.addEventListener('DOMContentLoaded', function() { // initialise the map centered on UK map = L.map('map', { scrollWheelZoom: true, // Enable scroll wheel zoom zoomControl: true // Show zoom controls }).setView([54.5, -2], 6); // Add OpenStreetMap tiles L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { maxZoom: 19, attribution: '© OpenStreetMap contributors' }).addTo(map); // Get facilities data from sessionStorage facilities = JSON.parse(sessionStorage.getItem('facilityData') || '[]'); // Add location found handler map.on('locationfound', function(e) { try { const { lat, lng } = e.latlng; // Update the map directly with the coordinates updateMapLocation({ lat, lng }, currentRadius); // Remove overlay once we have a valid location const overlay = document.getElementById('mapOverlay'); if (overlay) { overlay.classList.add('hidden'); } // Get postcode from coordinates fetch(`https://api.postcodes.io/postcodes?lon=${lng}&lat=${lat}`) .then(response => response.json()) .then(data => { if (data.status === 200 && data.result && data.result.length > 0) { const postcode = data.result[0].postcode; const postcodeInput = document.getElementById('postcode'); if (postcodeInput) { postcodeInput.value = postcode; } } }) .catch(error => { console.error('Error getting postcode:', error); }); } catch (error) { console.error('Error processing location:', error); alert('Error getting your location: ' + error.message); } }); // Add location error handler map.on('locationerror', function(e) { console.error('Geolocation error:', e); let message = 'Error getting your location: '; switch(e.code) { case 1: // PERMISSION_DENIED message += 'Please enable location access in your browser settings.'; break; case 2: // POSITION_UNAVAILABLE message += 'Location information is unavailable.'; break; case 3: // TIMEOUT message += 'Location request timed out.'; break; default: message += 'An unknown error occurred.'; } alert(message); }); // Set up form handlers setupFormHandlers(); // Set up search handler from header setupHeaderSearchHandler(); }); /** * Set up form handlers for postcode and radius inputs */ function setupFormHandlers() { const postcodeForm = document.getElementById('postcodeForm'); const radiusSelect = document.getElementById('radius'); if (postcodeForm) { // Add geolocation functionality to the search button const searchButton = postcodeForm.querySelector('button[type="submit"]'); if (searchButton) { searchButton.onclick = (e) => { // If the postcode input is empty, use geolocation const postcodeInput = document.getElementById('postcode'); if (!postcodeInput.value.trim()) { e.preventDefault(); map.locate({ setView: false, enableHighAccuracy: true, timeout: 10000, maximumAge: 0 }); } }; } postcodeForm.addEventListener('submit', async function(e) { e.preventDefault(); const postcode = document.getElementById('postcode').value; const radius = parseFloat(document.getElementById('radius').value); // Show loading state const submitButton = this.querySelector('button[type="submit"]'); submitButton.disabled = true; // Validate postcode format first if (!isValidPostcode(postcode)) { submitButton.disabled = false; submitButton.innerHTML = originalButtonContent; alert('Please enter a valid UK postcode'); return; } try { // Get coordinates for postcode const coords = await getPostcodeCoordinates(postcode); if (!coords) { throw new Error('Could not find coordinates for this postcode'); } // Update map with new location and radius updateMapLocation(coords, radius); // Remove overlay once we have a valid postcode const overlay = document.getElementById('mapOverlay'); if (overlay) { overlay.classList.add('hidden'); } // Store current postcode currentPostcode = postcode; currentRadius = radius; } catch (error) { console.error('Error processing postcode:', error); alert(error.message || 'Error processing postcode'); } finally { // Always reset button state submitButton.disabled = false; } }); } if (radiusSelect) { radiusSelect.addEventListener('change', async function(e) { e.preventDefault(); const postcode = document.getElementById('postcode').value; const coords = await getPostcodeCoordinates(postcode); const radius = parseFloat(this.value); updateMapLocation(coords, radius); }); } } /** * Validate UK postcode format * @param {string} postcode - The postcode to validate * @returns {boolean} True if valid, false otherwise */ function isValidPostcode(postcode) { // Basic UK postcode regex const postcodeRegex = /^[A-Z]{1,2}[0-9][A-Z0-9]? ?[0-9][A-Z]{2}$/i; return postcodeRegex.test(postcode.trim()); } /** * Get coordinates for a UK postcode using postcodes.io API * @param {string} postcode - The postcode to geocode * @returns {Promise<{lat: number, lng: number}>} The coordinates */ async function getPostcodeCoordinates(postcode) { try { const response = await fetch(`https://api.postcodes.io/postcodes/${encodeURIComponent(postcode)}`); if (!response.ok) { throw new Error('Postcode not found'); } const data = await response.json(); if (data.status === 200 && data.result) { return { lat: data.result.latitude, lng: data.result.longitude }; } throw new Error('Invalid response from postcode API'); } catch (error) { console.error('Error getting postcode coordinates:', error); throw error; } } /** * Update map location and display facilities within radius * @param {Object} coords - The coordinates to center on (null to use existing) * @param {number} radius - The radius in miles */ function updateMapLocation(coords, radius) { // Clear existing markers and circle clearMapOverlays(); // Get center coordinates (either new or existing) const center = coords || map.getCenter(); // Convert radius from miles to meters (1 mile = 1609.34 meters) const radiusMeters = radius * 1609.34; // Add circle for radius circle = L.circle([center.lat, center.lng], { color: '#198754', fillColor: '#198754', fillOpacity: 0.1, radius: radiusMeters }).addTo(map); // Find facilities within radius const facilitiesInRange = findFacilitiesInRange(center, radius); // Add markers for facilities facilitiesInRange.forEach(facility => { const marker = L.marker([facility.lat, facility.lng]) .bindPopup(createPopupContent(facility)) .addTo(map); markers.push(marker); }); // Fit map bounds to circle map.fitBounds(circle.getBounds()); // Update facility list updateFacilityList(facilitiesInRange); } /** * Clear all markers and circle from map */ function clearMapOverlays() { // Clear markers markers.forEach(marker => marker.remove()); markers = []; // Clear circle if (circle) { circle.remove(); circle = null; } } /** * Find facilities within specified radius of center point * @param {Object} center - The center coordinates * @param {number} radius - The radius in miles * @returns {Array} Array of facilities within range */ function findFacilitiesInRange(center, radius) { return facilities.filter(facility => { const distance = calculateDistance( center.lat, center.lng, parseFloat(facility.lat), parseFloat(facility.lng) ); return distance <= radius; }); } /** * Calculate distance between two points using Haversine formula * @param {number} lat1 - Latitude of first point * @param {number} lon1 - Longitude of first point * @param {number} lat2 - Latitude of second point * @param {number} lon2 - Longitude of second point * @returns {number} Distance in miles */ function calculateDistance(lat1, lon1, lat2, lon2) { const R = 3959; // Earth's radius in miles const dLat = toRad(lat2 - lat1); const dLon = toRad(lon2 - lon1); const a = Math.sin(dLat/2) * Math.sin(dLat/2) + Math.cos(toRad(lat1)) * Math.cos(toRad(lat2)) * Math.sin(dLon/2) * Math.sin(dLon/2); const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a)); return R * c; } /** * Convert degrees to radians * @param {number} degrees - Value in degrees * @returns {number} Value in radians */ function toRad(degrees) { return degrees * (Math.PI / 180); } /** * Create popup content for facility marker * @param {Object} facility - The facility data * @returns {string} HTML content for popup */ function createPopupContent(facility) { const isAuthenticated = window.auth && window.auth.isAuthenticated(); return `
${escapeHtml(facility.category)}
${escapeHtml(facility.description)}
Address:
${escapeHtml(formatAddress(facility))}
Added by: ${escapeHtml(facility.contributor)}
${isAuthenticated ? `${escapeHtml(facility.category)}
${distance.toFixed(1)} miles away${escapeHtml(facility.category)}
${escapeHtml(facility.description)}
Address:
${escapeHtml(formatAddress(facility))}
Added by: ${escapeHtml(facility.contributor)}