Spaces:
Sleeping
Sleeping
Upload index.html
Browse files- 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>
|