rajkhanke commited on
Commit
bbb785e
·
verified ·
1 Parent(s): 1095a90

Upload index.html

Browse files
Files changed (1) hide show
  1. templates/index.html +876 -0
templates/index.html ADDED
@@ -0,0 +1,876 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>Advanced Farm Geofencing & Weather Dashboard</title>
7
+ <!-- External CSS -->
8
+ <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" />
9
+ <link href="https://cdn.jsdelivr.net/npm/toastify-js/src/toastify.min.css" rel="stylesheet" />
10
+ <script src="https://cdn.tailwindcss.com"></script>
11
+ <style>
12
+ #map {
13
+ height: 70vh;
14
+ width: 100%;
15
+ border-radius: 8px;
16
+ box-shadow: 0 4px 6px rgba(0,0,0,0.1);
17
+ }
18
+ .control-panel {
19
+ background: rgba(255,255,255,0.9);
20
+ padding: 15px;
21
+ border-radius: 8px;
22
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
23
+ }
24
+ .weather-card, .event-card {
25
+ transition: transform 0.2s;
26
+ cursor: pointer;
27
+ }
28
+ .weather-card:hover, .event-card:hover {
29
+ transform: translateY(-5px);
30
+ }
31
+ .loading-overlay {
32
+ position: fixed;
33
+ top: 0;
34
+ left: 0;
35
+ width: 100%;
36
+ height: 100%;
37
+ background: rgba(255,255,255,0.8);
38
+ display: none;
39
+ justify-content: center;
40
+ align-items: center;
41
+ z-index: 9999;
42
+ }
43
+ .spinner {
44
+ width: 50px;
45
+ height: 50px;
46
+ border: 5px solid #f3f3f3;
47
+ border-top: 5px solid #3498db;
48
+ border-radius: 50%;
49
+ animation: spin 1s linear infinite;
50
+ }
51
+ @keyframes spin {
52
+ 0% { transform: rotate(0deg); }
53
+ 100% { transform: rotate(360deg); }
54
+ }
55
+
56
+ </style>
57
+ </head>
58
+ <body class="bg-gray-100">
59
+ <!-- Loading Overlay -->
60
+ <div id="loadingOverlay" class="loading-overlay">
61
+ <div class="spinner"></div>
62
+ </div>
63
+ <div class="container-fluid py-4">
64
+ <div class="row mb-4">
65
+ <div class="col-12">
66
+ <div class="d-flex justify-content-between align-items-center mb-4">
67
+ <h1 class="text-3xl font-bold">Advanced Farm Geofencing & Weather Dashboard</h1>
68
+ <div class="d-flex gap-2">
69
+ <button id="helpBtn" class="btn btn-info">
70
+ <i class="fas fa-question-circle"></i> Help
71
+ </button>
72
+ <button id="resetBtn" class="btn btn-warning">
73
+ <i class="fas fa-redo"></i> Reset
74
+ </button>
75
+ </div>
76
+ </div>
77
+ </div>
78
+ </div>
79
+ <div class="row">
80
+ <!-- Search & Control Panel -->
81
+ <div class="col-md-3">
82
+ <div class="control-panel mb-4">
83
+ <h5 class="mb-3">Search Location</h5>
84
+ <div class="mb-3">
85
+ <input type="text" id="addressInput" class="form-control mb-2" placeholder="Enter Address">
86
+ <button id="addressSearchBtn" class="btn btn-primary w-100">
87
+ <i class="fas fa-search"></i> Search Address
88
+ </button>
89
+ </div>
90
+ <div class="mb-3">
91
+ <div class="input-group mb-2">
92
+ <input type="number" id="latInput" class="form-control" placeholder="Latitude" step="0.000001">
93
+ <input type="number" id="lngInput" class="form-control" placeholder="Longitude" step="0.000001">
94
+ </div>
95
+ <button id="coordSearchBtn" class="btn btn-primary w-100">
96
+ <i class="fas fa-map-marker-alt"></i> Search Coordinates
97
+ </button>
98
+ </div>
99
+ </div>
100
+ <!-- Drawing Controls -->
101
+ <div class="control-panel mb-4">
102
+ <h5 class="mb-3">Drawing Tools</h5>
103
+ <div class="btn-group w-100 mb-2">
104
+ <button id="startDrawingBtn" class="btn btn-success">Start Drawing</button>
105
+ <button id="clearDrawingBtn" class="btn btn-danger">Clear</button>
106
+ </div>
107
+ <div class="form-check mt-2">
108
+ <input class="form-check-input" type="checkbox" id="snapToGridCheck">
109
+ <label class="form-check-label" for="snapToGridCheck">Snap to Grid</label>
110
+ </div>
111
+ </div>
112
+ <!-- Weather Settings -->
113
+ <div class="control-panel">
114
+ <h5 class="mb-3">Weather Settings</h5>
115
+ <div class="mb-3">
116
+ <label class="form-label">Radius (km)</label>
117
+ <input type="range" class="form-range" id="radiusSlider" min="50" max="500" step="50" value="200">
118
+ <span id="radiusValue">200 km</span>
119
+ </div>
120
+ </div>
121
+ </div>
122
+ <!-- Map & Data Display -->
123
+ <div class="col-md-9">
124
+ <div class="row">
125
+ <div class="col-12">
126
+ <div id="map"></div>
127
+ </div>
128
+ </div>
129
+ <!-- Tabbed Data Display -->
130
+ <div class="row mt-4">
131
+ <div class="col-12">
132
+ <div class="card">
133
+ <div class="card-body">
134
+ <ul class="nav nav-tabs" id="dataTabs">
135
+ <li class="nav-item">
136
+ <a class="nav-link active" data-bs-toggle="tab" href="#centerWeather">Center Weather</a>
137
+ </li>
138
+ <li class="nav-item">
139
+ <a class="nav-link" data-bs-toggle="tab" href="#surroundingWeather">Surrounding Areas</a>
140
+ </li>
141
+ <li class="nav-item">
142
+ <a class="nav-link" data-bs-toggle="tab" href="#disasterEvents">Disaster Events</a>
143
+ </li>
144
+ <li class="nav-item">
145
+ <a class="nav-link" data-bs-toggle="tab" href="#soilAnalysis">Soil Properties</a>
146
+ </li>
147
+ <li class="nav-item">
148
+ <a class="nav-link" data-bs-toggle="tab" href="#soilType">Soil Classification</a>
149
+ </li>
150
+ </ul>
151
+ <div class="tab-content mt-3">
152
+ <div class="tab-pane fade show active" id="centerWeather">
153
+ <div id="centerWeatherContent" class="row"></div>
154
+ </div>
155
+ <div class="tab-pane fade" id="surroundingWeather">
156
+ <div id="surroundingWeatherContent" class="row">
157
+ <div class="col-12">Surrounding weather markers are displayed on the map.</div>
158
+ <div id="surroundingWeatherTable" class="col-12 mt-3"></div>
159
+ </div>
160
+ </div>
161
+ <div class="tab-pane fade" id="disasterEvents">
162
+ <div id="disasterEventsContent" class="row">
163
+ <div class="col-12">Disaster events will be listed here.</div>
164
+ </div>
165
+ </div>
166
+ <div class="tab-pane fade" id="soilAnalysis">
167
+ <div id="soilAnalysisContent" class="row">
168
+ <div class="col-12">Soil properties data will appear here.</div>
169
+ </div>
170
+ </div>
171
+ <div class="tab-pane fade" id="soilType">
172
+ <div id="soilTypeContent" class="row">
173
+ <div class="col-12">Soil classification details will appear here.</div>
174
+ </div>
175
+ </div>
176
+ </div>
177
+ </div>
178
+ </div>
179
+ </div>
180
+ </div>
181
+ </div>
182
+ </div>
183
+ </div>
184
+
185
+ <!-- Help Modal -->
186
+ <div class="modal fade" id="helpModal" tabindex="-1">
187
+ <div class="modal-dialog">
188
+ <div class="modal-content">
189
+ <div class="modal-header">
190
+ <h5 class="modal-title">How to Use the Dashboard</h5>
191
+ <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
192
+ </div>
193
+ <div class="modal-body">
194
+ <h6>Drawing a Farm Area</h6>
195
+ <ol>
196
+ <li>Click "Start Drawing" to toggle draw mode.</li>
197
+ <li>Click on the map to add vertices; double-click (or click the first point) to finish.</li>
198
+ <li>Use "Clear" to remove the drawn area.</li>
199
+ </ol>
200
+ <h6>Weather Information</h6>
201
+ <ul>
202
+ <li>The Center Weather tab shows detailed conditions with a weather symbol and description.</li>
203
+ <li>The Surrounding Areas tab displays markers on the map and a table listing location, temperature, humidity, pressure, and cloud cover for 10 random surrounding points.</li>
204
+ </ul>
205
+ <h6>Disaster Events</h6>
206
+ <ul>
207
+ <li>Disaster events (e.g. floods, earthquakes) are displayed on the map as icons.</li>
208
+ <li>Hover over an icon to view details such as title, description, date, and type.</li>
209
+ <li>The Disaster Events tab lists events in a table with columns for Date, Title, Description, Location, and Type.</li>
210
+ </ul>
211
+ <h6>Soil Data</h6>
212
+ <ul>
213
+ <li>The "Soil Properties" tab shows soil parameters in a table format.</li>
214
+ <li>The "Soil Classification" tab displays classification details and associated probabilities.</li>
215
+ </ul>
216
+ </div>
217
+ </div>
218
+ </div>
219
+ </div>
220
+
221
+ <!-- External Scripts -->
222
+ <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
223
+ <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
224
+ <script src="https://kit.fontawesome.com/your-fontawesome-kit.js"></script>
225
+ <script src="https://cdn.jsdelivr.net/npm/toastify-js"></script>
226
+ <script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyBvVLjWmCja331H8SuIZ4UlJdZytuYkC6Y&libraries=drawing,places"></script>
227
+
228
+ <!-- Custom JavaScript -->
229
+ <script>
230
+ let map, drawingManager, polygon, markers = [], disasterMarkers = [];
231
+ let centerMarker, soilMarker;
232
+ let isDrawing = false;
233
+ let infoWindow;
234
+ let geocoder;
235
+
236
+ // Reverse geocode helper – returns formatted address via callback
237
+ function reverseGeocode(lat, lng, callback) {
238
+ geocoder.geocode({location: {lat: lat, lng: lng}}, function(results, status) {
239
+ if (status === 'OK' && results[0]) {
240
+ callback(results[0].formatted_address);
241
+ } else {
242
+ callback("Unknown location");
243
+ }
244
+ });
245
+ }
246
+
247
+ // Returns weather description based on weather code
248
+ function getWeatherDescription(weathercode) {
249
+ if (weathercode === 0) return "Clear sky";
250
+ else if ([1,2,3].includes(weathercode)) return "Mainly clear to overcast";
251
+ else if ([45,48].includes(weathercode)) return "Foggy conditions";
252
+ else if ([51,53,55].includes(weathercode)) return "Light drizzle";
253
+ else if ([61,63,65].includes(weathercode)) return "Rainy conditions";
254
+ else if ([66,67].includes(weathercode)) return "Freezing rain";
255
+ else if ([71,73,75,77].includes(weathercode)) return "Snow fall";
256
+ else if ([80,81,82].includes(weathercode)) return "Rain showers";
257
+ else if ([85,86].includes(weathercode)) return "Snow showers";
258
+ else if ([95,96,99].includes(weathercode)) return "Thunderstorm";
259
+ return "Unknown weather";
260
+ }
261
+
262
+ // Format ISO time string into local time (HH:MM)
263
+ function formatTime(isoStr) {
264
+ let date = new Date(isoStr);
265
+ return date.toLocaleTimeString([], {hour: '2-digit', minute: '2-digit'});
266
+ }
267
+
268
+ // Returns weather icon URL based on weather code
269
+ function getWeatherIcon(weathercode) {
270
+ if (weathercode === 0) return "https://img.icons8.com/emoji/48/000000/sun-emoji.png";
271
+ else if ([1,2,3].includes(weathercode)) return "https://img.icons8.com/emoji/48/000000/sun-behind-cloud.png";
272
+ else if ([45,48].includes(weathercode)) return "https://img.icons8.com/emoji/48/000000/fog.png";
273
+ else if ([51,53,55].includes(weathercode)) return "https://img.icons8.com/emoji/48/000000/cloud-with-rain.png";
274
+ else if ([61,63,65].includes(weathercode)) return "https://img.icons8.com/emoji/48/000000/cloud-with-rain.png";
275
+ else if ([66,67].includes(weathercode)) return "https://img.icons8.com/emoji/48/000000/cloud-with-rain.png";
276
+ else if ([71,73,75,77].includes(weathercode)) return "https://img.icons8.com/emoji/48/000000/snowflake.png";
277
+ else if ([80,81,82].includes(weathercode)) return "https://img.icons8.com/emoji/48/000000/cloud-with-rain.png";
278
+ else if ([85,86].includes(weathercode)) return "https://img.icons8.com/emoji/48/000000/snowflake.png";
279
+ else if ([95,96,99].includes(weathercode)) return "https://img.icons8.com/emoji/48/000000/cloud-with-lightning.png";
280
+ return "https://img.icons8.com/emoji/48/000000/question-mark-emoji.png";
281
+ }
282
+
283
+
284
+
285
+ // Classify disaster type based on event title or category keywords
286
+ function classifyEventType(event) {
287
+ let title = "";
288
+ if (event.title) {
289
+ title = event.title.toLowerCase();
290
+ } else if (event.category) {
291
+ title = event.category.toLowerCase();
292
+ }
293
+ if (title.indexOf("flood") !== -1) return "flood";
294
+ if (title.indexOf("earthquake") !== -1) return "earthquake";
295
+ if (title.indexOf("cyclone") !== -1 || title.indexOf("hurricane") !== -1) return "cyclone";
296
+ if (title.indexOf("wildfire") !== -1 || title.indexOf("fire") !== -1) return "wildfire";
297
+ if (title.indexOf("tornado") !== -1) return "tornado";
298
+ return "disaster";
299
+ }
300
+
301
+ function initMap() {
302
+ map = new google.maps.Map(document.getElementById('map'), {
303
+ center: { lat: 20.5937, lng: 78.9629 },
304
+ zoom: 5,
305
+ mapTypeControl: true,
306
+ fullscreenControl: true,
307
+ streetViewControl: true,
308
+ streetViewControlOptions: {
309
+ position: google.maps.ControlPosition.RIGHT_BOTTOM
310
+ }
311
+ });
312
+ geocoder = new google.maps.Geocoder();
313
+ setupDrawingManager();
314
+ setupEventListeners();
315
+ setupControls();
316
+ updateWeatherData();
317
+ }
318
+
319
+ // Toggle draw mode on click
320
+ document.getElementById('startDrawingBtn').addEventListener('click', function() {
321
+ if (drawingManager.getDrawingMode() == google.maps.drawing.OverlayType.POLYGON) {
322
+ drawingManager.setDrawingMode(null);
323
+ this.textContent = "Start Drawing";
324
+ } else {
325
+ drawingManager.setDrawingMode(google.maps.drawing.OverlayType.POLYGON);
326
+ this.textContent = "Stop Drawing";
327
+ }
328
+ });
329
+
330
+ function setupDrawingManager() {
331
+ drawingManager = new google.maps.drawing.DrawingManager({
332
+ drawingMode: null,
333
+ drawingControl: false,
334
+ polygonOptions: {
335
+ fillColor: '#4CAF50',
336
+ fillOpacity: 0.3,
337
+ strokeWeight: 2,
338
+ strokeColor: '#4CAF50'
339
+ }
340
+ });
341
+ drawingManager.setMap(map);
342
+ google.maps.event.addListener(drawingManager, 'polygoncomplete', function(poly) {
343
+ polygon = poly;
344
+ isDrawing = false;
345
+ document.getElementById('startDrawingBtn').textContent = "Start Drawing";
346
+ if (document.getElementById('snapToGridCheck').checked) {
347
+ snapPolygonToGrid(polygon);
348
+ }
349
+ let bounds = new google.maps.LatLngBounds();
350
+ polygon.getPath().forEach(function(latlng) {
351
+ bounds.extend(latlng);
352
+ });
353
+ let center = bounds.getCenter();
354
+ map.setCenter(center);
355
+ updateWeatherData();
356
+ });
357
+ }
358
+
359
+ function snapPolygonToGrid(poly) {
360
+ let path = poly.getPath();
361
+ for (let i = 0; i < path.getLength(); i++) {
362
+ let pt = path.getAt(i);
363
+ let snapped = snapToGrid({ latLng: pt });
364
+ path.setAt(i, snapped);
365
+ }
366
+ }
367
+
368
+ function snapToGrid(event) {
369
+ const gridSize = 0.01;
370
+ let lat = Math.round(event.latLng.lat() / gridSize) * gridSize;
371
+ let lng = Math.round(event.latLng.lng() / gridSize) * gridSize;
372
+ return new google.maps.LatLng(lat, lng);
373
+ }
374
+
375
+ function setupControls() {
376
+ const radiusSlider = document.getElementById('radiusSlider');
377
+ const radiusValue = document.getElementById('radiusValue');
378
+ radiusSlider.addEventListener('input', function() {
379
+ radiusValue.textContent = `${this.value} km`;
380
+ updateWeatherData();
381
+ });
382
+ document.getElementById('snapToGridCheck').addEventListener('change', function() {
383
+ if (polygon && this.checked) {
384
+ snapPolygonToGrid(polygon);
385
+ }
386
+ });
387
+ }
388
+
389
+ function setupEventListeners() {
390
+ map.addListener('click', function(e) {
391
+ if (!isDrawing) {
392
+ let lat = e.latLng.lat().toFixed(6);
393
+ let lng = e.latLng.lng().toFixed(6);
394
+ showToast(`Clicked location: ${lat}, ${lng}`);
395
+ }
396
+ });
397
+ document.getElementById('clearDrawingBtn').addEventListener('click', clearAll);
398
+ document.getElementById('helpBtn').addEventListener('click', function() {
399
+ new bootstrap.Modal(document.getElementById('helpModal')).show();
400
+ });
401
+ document.getElementById('resetBtn').addEventListener('click', function() {
402
+ clearAll();
403
+ map.setCenter({ lat: 20.5937, lng: 78.9629 });
404
+ map.setZoom(5);
405
+ updateWeatherData();
406
+ });
407
+ document.getElementById('addressSearchBtn').addEventListener('click', searchByAddress);
408
+ document.getElementById('coordSearchBtn').addEventListener('click', searchByCoordinates);
409
+ }
410
+
411
+ function clearAll() {
412
+ if (polygon) {
413
+ polygon.setMap(null);
414
+ polygon = null;
415
+ }
416
+ markers.forEach(marker => marker.setMap(null));
417
+ markers = [];
418
+ disasterMarkers.forEach(marker => marker.setMap(null));
419
+ disasterMarkers = [];
420
+ if (centerMarker) {
421
+ centerMarker.setMap(null);
422
+ centerMarker = null;
423
+ }
424
+ if (soilMarker) {
425
+ soilMarker.setMap(null);
426
+ soilMarker = null;
427
+ }
428
+ document.getElementById('surroundingWeatherContent').innerHTML = `<div class="col-12">Draw farm boundary to see surrounding weather.</div>`;
429
+ document.getElementById('surroundingWeatherTable').innerHTML = '';
430
+ document.getElementById('soilAnalysisContent').innerHTML = `<div class="col-12">Soil properties data will appear here.</div>`;
431
+ document.getElementById('soilTypeContent').innerHTML = `<div class="col-12">Soil classification details will appear here.</div>`;
432
+ document.getElementById('disasterEventsContent').innerHTML = `<div class="col-12">Disaster events will be listed here.</div>`;
433
+ }
434
+
435
+ function searchByAddress() {
436
+ let address = document.getElementById('addressInput').value;
437
+ if (!address) {
438
+ showToast("Please enter an address");
439
+ return;
440
+ }
441
+ const geocoderLocal = new google.maps.Geocoder();
442
+ geocoderLocal.geocode({ address: address }, function(results, status) {
443
+ if (status === google.maps.GeocoderStatus.OK) {
444
+ let location = results[0].geometry.location;
445
+ map.setCenter(location);
446
+ map.setZoom(12);
447
+ updateWeatherData();
448
+ } else {
449
+ showToast("Geocode was not successful: " + status);
450
+ }
451
+ });
452
+ }
453
+
454
+ function searchByCoordinates() {
455
+ let lat = parseFloat(document.getElementById('latInput').value);
456
+ let lng = parseFloat(document.getElementById('lngInput').value);
457
+ if (!isNaN(lat) && !isNaN(lng)) {
458
+ map.setCenter({ lat: lat, lng: lng });
459
+ updateWeatherData();
460
+ } else {
461
+ showToast("Invalid coordinates");
462
+ }
463
+ }
464
+
465
+ function updateWeatherData() {
466
+ showLoading(true);
467
+ let centerForWeather;
468
+ if (polygon) {
469
+ let bounds = new google.maps.LatLngBounds();
470
+ polygon.getPath().forEach(latlng => bounds.extend(latlng));
471
+ centerForWeather = bounds.getCenter();
472
+ } else {
473
+ centerForWeather = map.getCenter();
474
+ }
475
+ let lat = centerForWeather.lat();
476
+ let lng = centerForWeather.lng();
477
+ let radius = document.getElementById('radiusSlider').value;
478
+
479
+ // Fetch center weather
480
+ fetch(`/get_full_weather?lat=${lat}&lon=${lng}`)
481
+ .then(response => response.json())
482
+ .then(data => {
483
+ updateCenterWeatherUI(data);
484
+ let iconURL = getWeatherIcon(data.current_conditions.weathercode);
485
+ if (centerMarker) {
486
+ centerMarker.setPosition(centerForWeather);
487
+ centerMarker.setIcon({
488
+ url: iconURL,
489
+ scaledSize: new google.maps.Size(40, 40)
490
+ });
491
+ } else {
492
+ centerMarker = new google.maps.Marker({
493
+ position: centerForWeather,
494
+ map: map,
495
+ icon: {
496
+ url: iconURL,
497
+ scaledSize: new google.maps.Size(40, 40)
498
+ }
499
+ });
500
+ }
501
+ })
502
+ .catch(err => console.error("Error fetching center weather:", err));
503
+
504
+ // Fetch surrounding weather if polygon exists
505
+ if (polygon) {
506
+ fetch(`/get_circle_weather?lat=${lat}&lon=${lng}&radius=${radius}`)
507
+ .then(response => response.json())
508
+ .then(data => {
509
+ updateSurroundingWeatherTable(data);
510
+ markers.forEach(marker => marker.setMap(null));
511
+ markers = [];
512
+ data.forEach(point => {
513
+ let pos = { lat: point.location.lat, lng: point.location.lon };
514
+ let iconURL = getWeatherIcon(point.current_weather.weathercode);
515
+ let markerObj = new google.maps.Marker({
516
+ position: pos,
517
+ map: map,
518
+ icon: {
519
+ url: iconURL,
520
+ scaledSize: new google.maps.Size(40, 40)
521
+ }
522
+ });
523
+ reverseGeocode(point.location.lat, point.location.lon, function(address) {
524
+ markerObj.setTitle(address);
525
+ });
526
+ markerObj.addListener('mouseover', function() {
527
+ let daily = point.daily || {};
528
+ let description = getWeatherDescription(point.current_weather.weathercode);
529
+ let content = `<div class="p-2">
530
+ <h6>${description}</h6>
531
+ <p>Temperature: ${point.current_weather.temperature}°C</p>
532
+ <p>Min: ${daily.temperature_2m_min ? daily.temperature_2m_min[0] + "°C" : 'No data'}, Max: ${daily.temperature_2m_max ? daily.temperature_2m_max[0] + "°C" : 'No data'}</p>
533
+ <p>Pressure: ${point.current_weather.surface_pressure || 'No data'} hPa</p>
534
+ <p>Soil Moisture: ${point.current_weather.soil_moisture || 'No data'}</p>
535
+ <p>Humidity: ${point.current_weather.relativehumidity || 'No data'}%</p>
536
+ <p>Wind Speed: ${point.current_weather.windspeed || 'No data'} km/h</p>
537
+ <p>UV Index: ${point.current_weather.uv_index || 'No data'}</p>
538
+ <p>Cloud Cover: ${point.current_weather.cloudcover || 'No data'}%</p>
539
+ <p>Sunrise: ${daily.sunrise ? formatTime(daily.sunrise[0]) : 'No data'}, Sunset: ${daily.sunset ? formatTime(daily.sunset[0]) : 'No data'}</p>
540
+ </div>`;
541
+ if (infoWindow) infoWindow.close();
542
+ infoWindow = new google.maps.InfoWindow({ content: content });
543
+ infoWindow.open(map, markerObj);
544
+ });
545
+ markerObj.addListener('mouseout', function() {
546
+ if (infoWindow) infoWindow.close();
547
+ });
548
+ markers.push(markerObj);
549
+ });
550
+ })
551
+ .catch(err => console.error("Error fetching surrounding weather:", err));
552
+ } else {
553
+ document.getElementById('surroundingWeatherTable').innerHTML = '';
554
+ markers.forEach(marker => marker.setMap(null));
555
+ markers = [];
556
+ }
557
+
558
+ // Fetch disaster events using the Ambee API
559
+ fetch(`/get_india_disaster_events`)
560
+ .then(response => response.json())
561
+ .then(data => {
562
+ console.log('Ambee disaster events data:', data);
563
+ updateDisasterEventsUI(data);
564
+ })
565
+ .catch(err => console.error("Error fetching disaster events:", err));
566
+
567
+ // Fetch soil properties and update soil classification
568
+ updateSoilAnalysis();
569
+ updateSoilType();
570
+ showLoading(false);
571
+ }
572
+
573
+ function updateCenterWeatherUI(data) {
574
+ const container = document.getElementById('centerWeatherContent');
575
+ container.innerHTML = '';
576
+ if (data.current_conditions) {
577
+ let iconURL = getWeatherIcon(data.current_conditions.weathercode);
578
+ let description = getWeatherDescription(data.current_conditions.weathercode);
579
+ let daily = data.daily || {};
580
+ let sunrise = daily.sunrise ? formatTime(daily.sunrise[0]) : 'No data';
581
+ let sunset = daily.sunset ? formatTime(daily.sunset[0]) : 'No data';
582
+ container.innerHTML = `<div class="col-12 weather-card p-3 border">
583
+ <div class="d-flex align-items-center">
584
+ <img src="${iconURL}" alt="weather icon" class="me-3"/>
585
+ <div>
586
+ <h6>${description}</h6>
587
+ <p>Temperature: ${data.current_conditions.temperature}°C</p>
588
+ <p>Min: ${daily.temperature_2m_min ? daily.temperature_2m_min[0] + "°C" : 'No data'}, Max: ${daily.temperature_2m_max ? daily.temperature_2m_max[0] + "°C" : 'No data'}</p>
589
+ </div>
590
+ </div>
591
+ <p>Humidity: ${data.current_conditions.humidity || 'No data'}%</p>
592
+ <p>Precipitation: ${daily.precipitation_sum ? daily.precipitation_sum[0] + " mm" : 'No data'}</p>
593
+ <p>Cloud Cover: ${data.current_conditions.cloudcover || 'No data'}%</p>
594
+ <p>UV Index: ${data.current_conditions.uv_index || 'No data'}</p>
595
+ <p>Soil Moisture: ${data.current_conditions.soil_moisture || 'No data'}</p>
596
+ <p>Sunrise: ${sunrise}, Sunset: ${sunset}</p>
597
+ </div>`;
598
+ } else {
599
+ container.innerHTML = `<div class="col-12">No data available.</div>`;
600
+ }
601
+ }
602
+
603
+ function updateSurroundingWeatherTable(data) {
604
+ let container = document.getElementById('surroundingWeatherTable');
605
+ let promises = data.map(point => {
606
+ return new Promise(resolve => {
607
+ reverseGeocode(point.location.lat, point.location.lon, function(address) {
608
+ resolve({ point: point, address: address });
609
+ });
610
+ });
611
+ });
612
+ Promise.all(promises).then(results => {
613
+ let html = '<table class="table table-bordered"><thead><tr><th>Location</th><th>Coordinates</th><th>Temperature (°C)</th><th>Humidity (%)</th><th>Pressure (hPa)</th><th>Cloud Cover (%)</th></tr></thead><tbody>';
614
+ results.forEach(item => {
615
+ let pt = item.point;
616
+ html += `<tr>
617
+ <td>${item.address}</td>
618
+ <td>${pt.location.lat.toFixed(4)}, ${pt.location.lon.toFixed(4)}</td>
619
+ <td>${pt.current_weather.temperature || 'No data'}</td>
620
+ <td>${pt.current_weather.relativehumidity || 'No data'}</td>
621
+ <td>${pt.current_weather.surface_pressure || 'No data'}</td>
622
+ <td>${pt.current_weather.cloudcover || 'No data'}</td>
623
+ </tr>`;
624
+ });
625
+ html += '</tbody></table>';
626
+ container.innerHTML = html;
627
+ });
628
+ }
629
+
630
+ // Updated icon selection function that accepts both event type and event name
631
+ function getDisasterIcon(eventType, eventName) {
632
+ // Use eventName in lowercase; if it's empty, fall back to eventType
633
+ let name = eventName ? eventName.toLowerCase() : "";
634
+ if (!name && eventType) {
635
+ name = eventType.toLowerCase();
636
+ }
637
+
638
+ if (/\bflood\b/.test(name)) {
639
+ return "https://img.icons8.com/emoji/48/000000/water-wave.png";
640
+ }
641
+ if (/\b(thunderstorm|thunder)\b/.test(name)) {
642
+ return "https://img.icons8.com/emoji/48/000000/cloud-with-lightning.png";
643
+ }
644
+ if (/\btornado\b/.test(name)) {
645
+ return "https://img.icons8.com/emoji/48/000000/tornado-emoji.png";
646
+ }
647
+ if (/\b(cyclone|storm)\b/.test(name)) {
648
+ return "https://img.icons8.com/emoji/48/000000/cloud-with-lightning.png";
649
+ }
650
+ if (/\b(wildfire|fire)\b/.test(name)) {
651
+ return "https://img.icons8.com/emoji/48/000000/fire.png";
652
+ }
653
+ if (/\bearthquake\b/.test(name)) {
654
+ return "https://img.icons8.com/emoji/48/000000/earthquake.png";
655
+ }
656
+ return "https://img.icons8.com/emoji/48/000000/exclamation-mark-emoji.png";
657
+ }
658
+
659
+
660
+
661
+ function updateDisasterEventsUI(data) {
662
+ const container = document.getElementById('disasterEventsContent');
663
+ container.innerHTML = '';
664
+ // Clear existing markers from map
665
+ disasterMarkers.forEach(marker => marker.setMap(null));
666
+ disasterMarkers = [];
667
+
668
+ // Use "result" as per the Ambee API response
669
+ if (!data.result || !data.result.length) {
670
+ container.innerHTML = `<div class="col-12">No disaster events available.</div>`;
671
+ return;
672
+ }
673
+
674
+ // Filter unique events using event_name and date as a unique key
675
+ const uniqueMap = {};
676
+ const uniqueEvents = [];
677
+ data.result.forEach(event => {
678
+ const key = (event.event_name || '') + '|' + (event.date || '');
679
+ if (!uniqueMap[key]) {
680
+ uniqueMap[key] = true;
681
+ uniqueEvents.push(event);
682
+ }
683
+ });
684
+
685
+ // Build HTML table for disaster events
686
+ let tableHTML = `<table class="table table-bordered">
687
+ <thead>
688
+ <tr>
689
+ <th>Date</th>
690
+ <th>Event Name</th>
691
+ <th>Event Type</th>
692
+ <th>Location (Lat, Lng)</th>
693
+ </tr>
694
+ </thead>
695
+ <tbody>`;
696
+
697
+ uniqueEvents.forEach(event => {
698
+ const eventDate = event.date || 'N/A';
699
+ const eventName = event.event_name || 'No Title';
700
+ // Use provided event_type if available, otherwise "UNKNOWN"
701
+ const eventType = event.event_type ? event.event_type.toUpperCase() : "UNKNOWN";
702
+ const lat = event.lat;
703
+ const lng = event.lng;
704
+ const locationStr = (lat && lng) ? `${parseFloat(lat).toFixed(4)}, ${parseFloat(lng).toFixed(4)}` : 'N/A';
705
+
706
+ tableHTML += `<tr>
707
+ <td>${eventDate}</td>
708
+ <td>${eventName}</td>
709
+ <td>${eventType}</td>
710
+ <td>${locationStr}</td>
711
+ </tr>`;
712
+
713
+ // Place a marker on the map if location is available
714
+ if (lat && lng) {
715
+ const pos = { lat: parseFloat(lat), lng: parseFloat(lng) };
716
+ // Pass both eventType and eventName for icon selection
717
+ const iconURL = getDisasterIcon(eventType, eventName);
718
+ const marker = new google.maps.Marker({
719
+ position: pos,
720
+ map: map,
721
+ icon: {
722
+ url: iconURL,
723
+ scaledSize: new google.maps.Size(40, 40)
724
+ }
725
+ });
726
+ marker.addListener('mouseover', () => {
727
+ if (infoWindow) infoWindow.close();
728
+ infoWindow = new google.maps.InfoWindow({
729
+ content: `<div class="p-2">
730
+ <h6>${eventName}</h6>
731
+ <p><strong>Date:</strong> ${eventDate}</p>
732
+ <p><strong>Type:</strong> ${eventType}</p>
733
+ <p><strong>Location:</strong> ${locationStr}</p>
734
+ </div>`
735
+ });
736
+ infoWindow.open(map, marker);
737
+ });
738
+ marker.addListener('mouseout', () => {
739
+ if (infoWindow) infoWindow.close();
740
+ });
741
+ disasterMarkers.push(marker);
742
+ }
743
+ });
744
+
745
+ tableHTML += `</tbody></table>`;
746
+ container.innerHTML = tableHTML;
747
+ }
748
+
749
+
750
+ function updateSoilAnalysis() {
751
+ let centerForSoil;
752
+ if (polygon) {
753
+ let bounds = new google.maps.LatLngBounds();
754
+ polygon.getPath().forEach(latlng => bounds.extend(latlng));
755
+ centerForSoil = bounds.getCenter();
756
+ } else {
757
+ centerForSoil = map.getCenter();
758
+ }
759
+ let lat = centerForSoil.lat();
760
+ let lng = centerForSoil.lng();
761
+ fetch(`/get_soil_properties?lat=${lat}&lon=${lng}`)
762
+ .then(response => response.json())
763
+ .then(data => {
764
+ const container = document.getElementById('soilAnalysisContent');
765
+ if (data.soil_properties && data.soil_properties.length > 0) {
766
+ let html = '<div class="card p-3"><table class="table table-bordered"><thead><tr><th>Parameter</th><th>Value</th><th>Unit</th></tr></thead><tbody>';
767
+ data.soil_properties.forEach(item => {
768
+ html += `<tr><td>${item[0]}</td><td>${item[1]}</td><td>${item[2]}</td></tr>`;
769
+ });
770
+ html += '</tbody></table></div>';
771
+ container.innerHTML = html;
772
+ } else {
773
+ container.innerHTML = 'No soil properties data available.';
774
+ }
775
+ })
776
+ .catch(err => console.error("Error fetching soil properties:", err));
777
+ }
778
+
779
+ function updateSoilType() {
780
+ let centerForSoil;
781
+ if (polygon) {
782
+ let bounds = new google.maps.LatLngBounds();
783
+ polygon.getPath().forEach(latlng => bounds.extend(latlng));
784
+ centerForSoil = bounds.getCenter();
785
+ } else {
786
+ centerForSoil = map.getCenter();
787
+ }
788
+ let lat = centerForSoil.lat();
789
+ let lng = centerForSoil.lng();
790
+ fetch(`/get_soil_classification?lat=${lat}&lon=${lng}`)
791
+ .then(response => response.json())
792
+ .then(data => {
793
+ updateSoilTypeUI(data);
794
+ if (soilMarker) {
795
+ soilMarker.setMap(null);
796
+ soilMarker = null;
797
+ }
798
+ if (data.soil_type && data.soil_category !== "unknown") {
799
+ let iconURL = getSoilIcon(data.soil_category);
800
+ let soilMarkerPosition = centerForSoil;
801
+ if (polygon) {
802
+ soilMarkerPosition = new google.maps.LatLng(
803
+ centerForSoil.lat() + 0.03,
804
+ centerForSoil.lng() + 0.03
805
+ );
806
+ }
807
+ soilMarker = new google.maps.Marker({
808
+ position: soilMarkerPosition,
809
+ map: map,
810
+ icon: {
811
+ url: iconURL,
812
+ scaledSize: new google.maps.Size(40, 40)
813
+ }
814
+ });
815
+ soilMarker.addListener('mouseover', function() {
816
+ if (infoWindow) infoWindow.close();
817
+ infoWindow = new google.maps.InfoWindow({
818
+ content: `<div class="p-2"><h6>Soil Classification</h6><p>${data.soil_type} (${data.soil_category})</p></div>`
819
+ });
820
+ infoWindow.open(map, soilMarker);
821
+ });
822
+ soilMarker.addListener('mouseout', function() {
823
+ if (infoWindow) infoWindow.close();
824
+ });
825
+ }
826
+ })
827
+ .catch(err => console.error("Error fetching soil classification:", err));
828
+ }
829
+
830
+ function updateSoilTypeUI(data) {
831
+ let container = document.getElementById('soilTypeContent');
832
+ if (data.soil_type && data.soil_category !== "unknown") {
833
+ let html = `<div class="card p-3">
834
+ <h6>Soil Classification</h6>
835
+ <p>Dominant Soil Type: ${data.soil_type} (${data.soil_category})</p>`;
836
+ if (data.soil_probabilities && data.soil_probabilities.length > 0) {
837
+ html += `<div class="mt-3"><h6>Probabilities</h6><table class="table table-bordered"><thead><tr><th>Soil Type</th><th>Probability (%)</th></tr></thead><tbody>`;
838
+ data.soil_probabilities.forEach(prob => {
839
+ html += `<tr><td>${prob[0]}</td><td>${prob[1]}</td></tr>`;
840
+ });
841
+ html += '</tbody></table></div>';
842
+ }
843
+ html += `</div>`;
844
+ container.innerHTML = html;
845
+ } else {
846
+ container.innerHTML = 'No soil classification data available.';
847
+ }
848
+ }
849
+
850
+ function getSoilIcon(soil_category) {
851
+ if (soil_category === "black") return "https://img.icons8.com/emoji/48/000000/black-diamond-emoji.png";
852
+ else if (soil_category === "red") return "https://img.icons8.com/emoji/48/ff0000/red-diamond-emoji.png";
853
+ else if (soil_category === "alluvial") return "https://img.icons8.com/emoji/48/008000/green-diamond-emoji.png";
854
+ else if (soil_category === "desert") return "https://img.icons8.com/emoji/48/8b4513/brown-diamond-emoji.png";
855
+ return "";
856
+ }
857
+
858
+ function showLoading(show) {
859
+ document.getElementById('loadingOverlay').style.display = show ? 'flex' : 'none';
860
+ }
861
+
862
+ function showToast(message) {
863
+ Toastify({
864
+ text: message,
865
+ duration: 3000,
866
+ gravity: "top",
867
+ position: "right",
868
+ backgroundColor: "#333",
869
+ stopOnFocus: true
870
+ }).showToast();
871
+ }
872
+
873
+ window.onload = initMap;
874
+ </script>
875
+ </body>
876
+ </html>