Quivara's picture
Fresh upload with LFS
bdb271a
// 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"];
}