Spaces:
Running
Running
| // 1. EXTENDED COORDINATE DATABASE | |
| // defines a static database of coordinates for Philippine cities and provinces | |
| const cityCoords = { | |
| // --- NCR --- | |
| "Manila": [14.5995, 120.9842], | |
| "Quezon City": [14.6760, 121.0437], | |
| "Makati": [14.5547, 121.0244], | |
| "Taguig": [14.5176, 121.0509], | |
| "Pasig": [14.5763, 121.0851], | |
| "Mandaluyong": [14.5794, 121.0359], | |
| "Marikina": [14.6333, 121.0980], | |
| "Las Pinas": [14.4445, 120.9939], | |
| "Muntinlupa": [14.4081, 121.0415], | |
| "Caloocan": [14.6401, 120.9745], | |
| "Parañaque": [14.4793, 121.0198], | |
| "Valenzuela": [14.7011, 120.9830], | |
| "Pasay": [14.5378, 121.0014], | |
| "Malabon": [14.6625, 120.9512], | |
| "Navotas": [14.6732, 120.9350], | |
| "San Juan": [14.6019, 121.0355], | |
| "Pateros": [14.5454, 121.0687], | |
| // --- CAVITE --- | |
| "Cavite": [14.2831, 120.9168], | |
| "Naic": [14.3168, 120.7628], | |
| "Bacoor": [14.4624, 120.9645], | |
| "Imus": [14.4297, 120.9367], | |
| "Dasmarinas": [14.3294, 120.9367], | |
| "General Trias": [14.3876, 120.8842], | |
| "Tagaytay": [14.1153, 120.9621], | |
| "Kawit": [14.4448, 120.9022], | |
| "Noveleta": [14.4263, 120.8820], | |
| "Rosario": [14.4153, 120.8532], | |
| "Tanza": [14.3949, 120.8532], | |
| "Silang": [14.2312, 120.9746], | |
| "Trece Martires": [14.2883, 120.8677], | |
| // --- LAGUNA --- | |
| "Laguna": [14.2166, 121.1667], | |
| "Calamba": [14.2142, 121.1553], | |
| "Santa Rosa": [14.3121, 121.1132], | |
| "Binan": [14.3400, 121.0827], | |
| "San Pedro": [14.3644, 121.0370], | |
| "Cabuyao": [14.2796, 121.1219], | |
| "Los Banos": [14.1708, 121.2413], | |
| // --- RIZAL --- | |
| "Rizal": [14.5906, 121.2236], | |
| "Antipolo": [14.5844, 121.1763], | |
| "Cainta": [14.5760, 121.1213], | |
| "Taytay": [14.5623, 121.1376], | |
| "San Mateo": [14.6963, 121.1215], | |
| "Binangonan": [14.4759, 121.1893], | |
| // --- BULACAN --- | |
| "Bulacan": [14.8524, 120.8228], | |
| "Malolos": [14.8527, 120.8160], | |
| "Meycauayan": [14.7356, 120.9622], | |
| "San Jose del Monte": [14.8143, 121.0427], | |
| "Bocaue": [14.8066, 120.9256], | |
| // --- MAJOR PROVINCES / CITIES --- | |
| "Pampanga": [15.0359, 120.6924], | |
| "Tarlac": [15.4802, 120.5979], | |
| "Batangas": [13.7565, 121.0583], | |
| "Baguio": [16.4023, 120.5960], | |
| "Cebu": [10.3157, 123.8854], | |
| "Iloilo": [10.7202, 122.5621], | |
| "Davao": [7.1907, 125.4553], | |
| "Cagayan": [17.6133, 121.7302], | |
| "Bicol": [13.4350, 123.4100], | |
| "Albay": [13.1391, 123.7438], | |
| "Tacloban": [11.2442, 125.0039], | |
| "Zamboanga": [6.9214, 122.0790], | |
| "Palawan": [9.8349, 118.7384], | |
| "Mindoro": [13.0264, 121.2227], | |
| "Isabela": [16.9754, 121.8107], | |
| "Pangasinan": [15.9236, 120.3392], | |
| "Philippines": [12.8797, 121.7740] // CENTER OF PH | |
| }; | |
| let map; | |
| let markers = []; | |
| // initializes the map view and starts fetching data | |
| document.addEventListener("DOMContentLoaded", () => { | |
| // sets the initial map center on the CALABARZON/NCR area | |
| map = L.map('map').setView([14.40, 121.00], 10); | |
| // adds the dark-themed tile layer to the map | |
| L.tileLayer('https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png', { | |
| attribution: '© OpenStreetMap © CARTO', | |
| subdomains: 'abcd', | |
| maxZoom: 19 | |
| }).addTo(map); | |
| console.log("ALISTO Map: Loaded"); | |
| // fetches the first set of post data | |
| fetchDataAndPlot(); | |
| // sets a timer to refresh data every 30 seconds | |
| setInterval(fetchDataAndPlot, 30000); | |
| }); | |
| // fetches the list of active disaster posts from the API | |
| function fetchDataAndPlot() { | |
| fetch('/api/posts') | |
| .then(response => response.json()) | |
| .then(data => { | |
| // updates the alert count in the map sidebar | |
| updateSidebar(data); | |
| // plots the markers on the map | |
| plotMarkers(data); | |
| }) | |
| .catch(err => console.error("Map Fetch Error:", err)); | |
| } | |
| // updates the displayed alert count and system status in the floating sidebar | |
| function updateSidebar(data) { | |
| const countEl = document.getElementById('alert-count'); | |
| const statusEl = document.getElementById('status-text'); | |
| if(countEl) countEl.innerText = data.length; | |
| if(statusEl) statusEl.innerText = "System Active"; | |
| } | |
| // clears existing markers and plots new circular markers for each post | |
| function plotMarkers(posts) { | |
| // removes all existing markers from the map | |
| markers.forEach(m => map.removeLayer(m)); | |
| markers = []; | |
| // iterates through all posts to find coordinates and draw markers | |
| posts.forEach(async post => { | |
| // asynchronously finds the latitude and longitude for the location | |
| let coords = await getCoordinatesSmart(post.location); | |
| if (coords) { | |
| // sets color and size based on post urgency level | |
| const urgencyColor = post.urgency_level === 'High' ? '#ff4444' : '#ed4801'; | |
| const radius = post.urgency_level === 'High' ? 14 : 8; | |
| // creates and adds a circular marker (L.circleMarker) to the map | |
| const circle = L.circleMarker(coords, { | |
| color: urgencyColor, | |
| fillColor: urgencyColor, | |
| fillOpacity: 0.7, | |
| radius: radius | |
| }).addTo(map); | |
| const timeStr = new Date(post.timestamp).toLocaleTimeString(); | |
| // binds a detailed pop-up box to the circular marker | |
| circle.bindPopup(` | |
| <div style="font-family: 'Roboto', sans-serif; color: #333; min-width: 200px;"> | |
| <strong style="text-transform:uppercase; color: #d32f2f; font-size: 1.1em;"> | |
| ${post.disaster_type} | |
| </strong> | |
| <div style="color: #ed4801; font-weight: 700; font-size: 0.9em; margin-bottom: 4px; text-transform: uppercase;"> | |
| ⚠ ${post.assistance_type || "General Help"} | |
| </div> | |
| <span style="font-size: 0.9em; color: #555;">📍 ${post.location}</span> | |
| <hr style="margin:8px 0; border:0; border-top:1px solid #ccc;"> | |
| <div style="font-size: 0.9em; margin-bottom: 5px; font-weight: 500;"> | |
| "${post.title}" | |
| </div> | |
| <small style="color: #888;">${timeStr}</small> | |
| </div> | |
| `); | |
| // adds the new marker to the global array | |
| markers.push(circle); | |
| } | |
| }); | |
| } | |
| // --- NEW SMART FINDER (Hybrid: Static List + API) --- | |
| // function to look up coordinates, prioritizing the static list then Nominatim API | |
| async function getCoordinatesSmart(locationStr) { | |
| if (!locationStr) return cityCoords["Philippines"]; | |
| // 1. Try Static List (Instant) | |
| // direct match check | |
| const exactMatch = Object.keys(cityCoords).find(k => k.toLowerCase() === locationStr.toLowerCase()); | |
| if (exactMatch) return cityCoords[exactMatch]; | |
| // fuzzy match check | |
| const fuzzyKey = Object.keys(cityCoords).find(city => locationStr.toLowerCase().includes(city.toLowerCase())); | |
| if (fuzzyKey) return cityCoords[fuzzyKey]; | |
| // 2. Try OpenStreetMap API (Dynamic) | |
| // checks local cache before making a network request | |
| if (!window.coordCache) window.coordCache = {}; | |
| if (window.coordCache[locationStr]) return window.coordCache[locationStr]; | |
| try { | |
| console.log(`Fetching coords for: ${locationStr}...`); | |
| // fetches coordinates from the Nominatim API | |
| const response = await fetch(`https://nominatim.openstreetmap.org/search?format=json&q=${locationStr}, Philippines`); | |
| const data = await response.json(); | |
| // processes and caches the result | |
| if (data && data.length > 0) { | |
| const lat = parseFloat(data[0].lat); | |
| const lon = parseFloat(data[0].lon); | |
| const result = [lat, lon]; | |
| window.coordCache[locationStr] = result; | |
| return result; | |
| } | |
| } catch (e) { | |
| console.error("Geocoding failed:", e); | |
| } | |
| // 3. Fallback | |
| // returns the center of the Philippines if geocoding fails | |
| return cityCoords["Philippines"]; | |
| } |