File size: 8,824 Bytes
bdb271a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
// 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"];
}