rajkhanke's picture
Upload index.html
bbb785e verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Advanced Farm Geofencing & Weather Dashboard</title>
<!-- External CSS -->
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" />
<link href="https://cdn.jsdelivr.net/npm/toastify-js/src/toastify.min.css" rel="stylesheet" />
<script src="https://cdn.tailwindcss.com"></script>
<style>
#map {
height: 70vh;
width: 100%;
border-radius: 8px;
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
}
.control-panel {
background: rgba(255,255,255,0.9);
padding: 15px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.weather-card, .event-card {
transition: transform 0.2s;
cursor: pointer;
}
.weather-card:hover, .event-card:hover {
transform: translateY(-5px);
}
.loading-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(255,255,255,0.8);
display: none;
justify-content: center;
align-items: center;
z-index: 9999;
}
.spinner {
width: 50px;
height: 50px;
border: 5px solid #f3f3f3;
border-top: 5px solid #3498db;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
</style>
</head>
<body class="bg-gray-100">
<!-- Loading Overlay -->
<div id="loadingOverlay" class="loading-overlay">
<div class="spinner"></div>
</div>
<div class="container-fluid py-4">
<div class="row mb-4">
<div class="col-12">
<div class="d-flex justify-content-between align-items-center mb-4">
<h1 class="text-3xl font-bold">Advanced Farm Geofencing & Weather Dashboard</h1>
<div class="d-flex gap-2">
<button id="helpBtn" class="btn btn-info">
<i class="fas fa-question-circle"></i> Help
</button>
<button id="resetBtn" class="btn btn-warning">
<i class="fas fa-redo"></i> Reset
</button>
</div>
</div>
</div>
</div>
<div class="row">
<!-- Search & Control Panel -->
<div class="col-md-3">
<div class="control-panel mb-4">
<h5 class="mb-3">Search Location</h5>
<div class="mb-3">
<input type="text" id="addressInput" class="form-control mb-2" placeholder="Enter Address">
<button id="addressSearchBtn" class="btn btn-primary w-100">
<i class="fas fa-search"></i> Search Address
</button>
</div>
<div class="mb-3">
<div class="input-group mb-2">
<input type="number" id="latInput" class="form-control" placeholder="Latitude" step="0.000001">
<input type="number" id="lngInput" class="form-control" placeholder="Longitude" step="0.000001">
</div>
<button id="coordSearchBtn" class="btn btn-primary w-100">
<i class="fas fa-map-marker-alt"></i> Search Coordinates
</button>
</div>
</div>
<!-- Drawing Controls -->
<div class="control-panel mb-4">
<h5 class="mb-3">Drawing Tools</h5>
<div class="btn-group w-100 mb-2">
<button id="startDrawingBtn" class="btn btn-success">Start Drawing</button>
<button id="clearDrawingBtn" class="btn btn-danger">Clear</button>
</div>
<div class="form-check mt-2">
<input class="form-check-input" type="checkbox" id="snapToGridCheck">
<label class="form-check-label" for="snapToGridCheck">Snap to Grid</label>
</div>
</div>
<!-- Weather Settings -->
<div class="control-panel">
<h5 class="mb-3">Weather Settings</h5>
<div class="mb-3">
<label class="form-label">Radius (km)</label>
<input type="range" class="form-range" id="radiusSlider" min="50" max="500" step="50" value="200">
<span id="radiusValue">200 km</span>
</div>
</div>
</div>
<!-- Map & Data Display -->
<div class="col-md-9">
<div class="row">
<div class="col-12">
<div id="map"></div>
</div>
</div>
<!-- Tabbed Data Display -->
<div class="row mt-4">
<div class="col-12">
<div class="card">
<div class="card-body">
<ul class="nav nav-tabs" id="dataTabs">
<li class="nav-item">
<a class="nav-link active" data-bs-toggle="tab" href="#centerWeather">Center Weather</a>
</li>
<li class="nav-item">
<a class="nav-link" data-bs-toggle="tab" href="#surroundingWeather">Surrounding Areas</a>
</li>
<li class="nav-item">
<a class="nav-link" data-bs-toggle="tab" href="#disasterEvents">Disaster Events</a>
</li>
<li class="nav-item">
<a class="nav-link" data-bs-toggle="tab" href="#soilAnalysis">Soil Properties</a>
</li>
<li class="nav-item">
<a class="nav-link" data-bs-toggle="tab" href="#soilType">Soil Classification</a>
</li>
</ul>
<div class="tab-content mt-3">
<div class="tab-pane fade show active" id="centerWeather">
<div id="centerWeatherContent" class="row"></div>
</div>
<div class="tab-pane fade" id="surroundingWeather">
<div id="surroundingWeatherContent" class="row">
<div class="col-12">Surrounding weather markers are displayed on the map.</div>
<div id="surroundingWeatherTable" class="col-12 mt-3"></div>
</div>
</div>
<div class="tab-pane fade" id="disasterEvents">
<div id="disasterEventsContent" class="row">
<div class="col-12">Disaster events will be listed here.</div>
</div>
</div>
<div class="tab-pane fade" id="soilAnalysis">
<div id="soilAnalysisContent" class="row">
<div class="col-12">Soil properties data will appear here.</div>
</div>
</div>
<div class="tab-pane fade" id="soilType">
<div id="soilTypeContent" class="row">
<div class="col-12">Soil classification details will appear here.</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Help Modal -->
<div class="modal fade" id="helpModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">How to Use the Dashboard</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<h6>Drawing a Farm Area</h6>
<ol>
<li>Click "Start Drawing" to toggle draw mode.</li>
<li>Click on the map to add vertices; double-click (or click the first point) to finish.</li>
<li>Use "Clear" to remove the drawn area.</li>
</ol>
<h6>Weather Information</h6>
<ul>
<li>The Center Weather tab shows detailed conditions with a weather symbol and description.</li>
<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>
</ul>
<h6>Disaster Events</h6>
<ul>
<li>Disaster events (e.g. floods, earthquakes) are displayed on the map as icons.</li>
<li>Hover over an icon to view details such as title, description, date, and type.</li>
<li>The Disaster Events tab lists events in a table with columns for Date, Title, Description, Location, and Type.</li>
</ul>
<h6>Soil Data</h6>
<ul>
<li>The "Soil Properties" tab shows soil parameters in a table format.</li>
<li>The "Soil Classification" tab displays classification details and associated probabilities.</li>
</ul>
</div>
</div>
</div>
</div>
<!-- External Scripts -->
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
<script src="https://kit.fontawesome.com/your-fontawesome-kit.js"></script>
<script src="https://cdn.jsdelivr.net/npm/toastify-js"></script>
<script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyBvVLjWmCja331H8SuIZ4UlJdZytuYkC6Y&libraries=drawing,places"></script>
<!-- Custom JavaScript -->
<script>
let map, drawingManager, polygon, markers = [], disasterMarkers = [];
let centerMarker, soilMarker;
let isDrawing = false;
let infoWindow;
let geocoder;
// Reverse geocode helper – returns formatted address via callback
function reverseGeocode(lat, lng, callback) {
geocoder.geocode({location: {lat: lat, lng: lng}}, function(results, status) {
if (status === 'OK' && results[0]) {
callback(results[0].formatted_address);
} else {
callback("Unknown location");
}
});
}
// Returns weather description based on weather code
function getWeatherDescription(weathercode) {
if (weathercode === 0) return "Clear sky";
else if ([1,2,3].includes(weathercode)) return "Mainly clear to overcast";
else if ([45,48].includes(weathercode)) return "Foggy conditions";
else if ([51,53,55].includes(weathercode)) return "Light drizzle";
else if ([61,63,65].includes(weathercode)) return "Rainy conditions";
else if ([66,67].includes(weathercode)) return "Freezing rain";
else if ([71,73,75,77].includes(weathercode)) return "Snow fall";
else if ([80,81,82].includes(weathercode)) return "Rain showers";
else if ([85,86].includes(weathercode)) return "Snow showers";
else if ([95,96,99].includes(weathercode)) return "Thunderstorm";
return "Unknown weather";
}
// Format ISO time string into local time (HH:MM)
function formatTime(isoStr) {
let date = new Date(isoStr);
return date.toLocaleTimeString([], {hour: '2-digit', minute: '2-digit'});
}
// Returns weather icon URL based on weather code
function getWeatherIcon(weathercode) {
if (weathercode === 0) return "https://img.icons8.com/emoji/48/000000/sun-emoji.png";
else if ([1,2,3].includes(weathercode)) return "https://img.icons8.com/emoji/48/000000/sun-behind-cloud.png";
else if ([45,48].includes(weathercode)) return "https://img.icons8.com/emoji/48/000000/fog.png";
else if ([51,53,55].includes(weathercode)) return "https://img.icons8.com/emoji/48/000000/cloud-with-rain.png";
else if ([61,63,65].includes(weathercode)) return "https://img.icons8.com/emoji/48/000000/cloud-with-rain.png";
else if ([66,67].includes(weathercode)) return "https://img.icons8.com/emoji/48/000000/cloud-with-rain.png";
else if ([71,73,75,77].includes(weathercode)) return "https://img.icons8.com/emoji/48/000000/snowflake.png";
else if ([80,81,82].includes(weathercode)) return "https://img.icons8.com/emoji/48/000000/cloud-with-rain.png";
else if ([85,86].includes(weathercode)) return "https://img.icons8.com/emoji/48/000000/snowflake.png";
else if ([95,96,99].includes(weathercode)) return "https://img.icons8.com/emoji/48/000000/cloud-with-lightning.png";
return "https://img.icons8.com/emoji/48/000000/question-mark-emoji.png";
}
// Classify disaster type based on event title or category keywords
function classifyEventType(event) {
let title = "";
if (event.title) {
title = event.title.toLowerCase();
} else if (event.category) {
title = event.category.toLowerCase();
}
if (title.indexOf("flood") !== -1) return "flood";
if (title.indexOf("earthquake") !== -1) return "earthquake";
if (title.indexOf("cyclone") !== -1 || title.indexOf("hurricane") !== -1) return "cyclone";
if (title.indexOf("wildfire") !== -1 || title.indexOf("fire") !== -1) return "wildfire";
if (title.indexOf("tornado") !== -1) return "tornado";
return "disaster";
}
function initMap() {
map = new google.maps.Map(document.getElementById('map'), {
center: { lat: 20.5937, lng: 78.9629 },
zoom: 5,
mapTypeControl: true,
fullscreenControl: true,
streetViewControl: true,
streetViewControlOptions: {
position: google.maps.ControlPosition.RIGHT_BOTTOM
}
});
geocoder = new google.maps.Geocoder();
setupDrawingManager();
setupEventListeners();
setupControls();
updateWeatherData();
}
// Toggle draw mode on click
document.getElementById('startDrawingBtn').addEventListener('click', function() {
if (drawingManager.getDrawingMode() == google.maps.drawing.OverlayType.POLYGON) {
drawingManager.setDrawingMode(null);
this.textContent = "Start Drawing";
} else {
drawingManager.setDrawingMode(google.maps.drawing.OverlayType.POLYGON);
this.textContent = "Stop Drawing";
}
});
function setupDrawingManager() {
drawingManager = new google.maps.drawing.DrawingManager({
drawingMode: null,
drawingControl: false,
polygonOptions: {
fillColor: '#4CAF50',
fillOpacity: 0.3,
strokeWeight: 2,
strokeColor: '#4CAF50'
}
});
drawingManager.setMap(map);
google.maps.event.addListener(drawingManager, 'polygoncomplete', function(poly) {
polygon = poly;
isDrawing = false;
document.getElementById('startDrawingBtn').textContent = "Start Drawing";
if (document.getElementById('snapToGridCheck').checked) {
snapPolygonToGrid(polygon);
}
let bounds = new google.maps.LatLngBounds();
polygon.getPath().forEach(function(latlng) {
bounds.extend(latlng);
});
let center = bounds.getCenter();
map.setCenter(center);
updateWeatherData();
});
}
function snapPolygonToGrid(poly) {
let path = poly.getPath();
for (let i = 0; i < path.getLength(); i++) {
let pt = path.getAt(i);
let snapped = snapToGrid({ latLng: pt });
path.setAt(i, snapped);
}
}
function snapToGrid(event) {
const gridSize = 0.01;
let lat = Math.round(event.latLng.lat() / gridSize) * gridSize;
let lng = Math.round(event.latLng.lng() / gridSize) * gridSize;
return new google.maps.LatLng(lat, lng);
}
function setupControls() {
const radiusSlider = document.getElementById('radiusSlider');
const radiusValue = document.getElementById('radiusValue');
radiusSlider.addEventListener('input', function() {
radiusValue.textContent = `${this.value} km`;
updateWeatherData();
});
document.getElementById('snapToGridCheck').addEventListener('change', function() {
if (polygon && this.checked) {
snapPolygonToGrid(polygon);
}
});
}
function setupEventListeners() {
map.addListener('click', function(e) {
if (!isDrawing) {
let lat = e.latLng.lat().toFixed(6);
let lng = e.latLng.lng().toFixed(6);
showToast(`Clicked location: ${lat}, ${lng}`);
}
});
document.getElementById('clearDrawingBtn').addEventListener('click', clearAll);
document.getElementById('helpBtn').addEventListener('click', function() {
new bootstrap.Modal(document.getElementById('helpModal')).show();
});
document.getElementById('resetBtn').addEventListener('click', function() {
clearAll();
map.setCenter({ lat: 20.5937, lng: 78.9629 });
map.setZoom(5);
updateWeatherData();
});
document.getElementById('addressSearchBtn').addEventListener('click', searchByAddress);
document.getElementById('coordSearchBtn').addEventListener('click', searchByCoordinates);
}
function clearAll() {
if (polygon) {
polygon.setMap(null);
polygon = null;
}
markers.forEach(marker => marker.setMap(null));
markers = [];
disasterMarkers.forEach(marker => marker.setMap(null));
disasterMarkers = [];
if (centerMarker) {
centerMarker.setMap(null);
centerMarker = null;
}
if (soilMarker) {
soilMarker.setMap(null);
soilMarker = null;
}
document.getElementById('surroundingWeatherContent').innerHTML = `<div class="col-12">Draw farm boundary to see surrounding weather.</div>`;
document.getElementById('surroundingWeatherTable').innerHTML = '';
document.getElementById('soilAnalysisContent').innerHTML = `<div class="col-12">Soil properties data will appear here.</div>`;
document.getElementById('soilTypeContent').innerHTML = `<div class="col-12">Soil classification details will appear here.</div>`;
document.getElementById('disasterEventsContent').innerHTML = `<div class="col-12">Disaster events will be listed here.</div>`;
}
function searchByAddress() {
let address = document.getElementById('addressInput').value;
if (!address) {
showToast("Please enter an address");
return;
}
const geocoderLocal = new google.maps.Geocoder();
geocoderLocal.geocode({ address: address }, function(results, status) {
if (status === google.maps.GeocoderStatus.OK) {
let location = results[0].geometry.location;
map.setCenter(location);
map.setZoom(12);
updateWeatherData();
} else {
showToast("Geocode was not successful: " + status);
}
});
}
function searchByCoordinates() {
let lat = parseFloat(document.getElementById('latInput').value);
let lng = parseFloat(document.getElementById('lngInput').value);
if (!isNaN(lat) && !isNaN(lng)) {
map.setCenter({ lat: lat, lng: lng });
updateWeatherData();
} else {
showToast("Invalid coordinates");
}
}
function updateWeatherData() {
showLoading(true);
let centerForWeather;
if (polygon) {
let bounds = new google.maps.LatLngBounds();
polygon.getPath().forEach(latlng => bounds.extend(latlng));
centerForWeather = bounds.getCenter();
} else {
centerForWeather = map.getCenter();
}
let lat = centerForWeather.lat();
let lng = centerForWeather.lng();
let radius = document.getElementById('radiusSlider').value;
// Fetch center weather
fetch(`/get_full_weather?lat=${lat}&lon=${lng}`)
.then(response => response.json())
.then(data => {
updateCenterWeatherUI(data);
let iconURL = getWeatherIcon(data.current_conditions.weathercode);
if (centerMarker) {
centerMarker.setPosition(centerForWeather);
centerMarker.setIcon({
url: iconURL,
scaledSize: new google.maps.Size(40, 40)
});
} else {
centerMarker = new google.maps.Marker({
position: centerForWeather,
map: map,
icon: {
url: iconURL,
scaledSize: new google.maps.Size(40, 40)
}
});
}
})
.catch(err => console.error("Error fetching center weather:", err));
// Fetch surrounding weather if polygon exists
if (polygon) {
fetch(`/get_circle_weather?lat=${lat}&lon=${lng}&radius=${radius}`)
.then(response => response.json())
.then(data => {
updateSurroundingWeatherTable(data);
markers.forEach(marker => marker.setMap(null));
markers = [];
data.forEach(point => {
let pos = { lat: point.location.lat, lng: point.location.lon };
let iconURL = getWeatherIcon(point.current_weather.weathercode);
let markerObj = new google.maps.Marker({
position: pos,
map: map,
icon: {
url: iconURL,
scaledSize: new google.maps.Size(40, 40)
}
});
reverseGeocode(point.location.lat, point.location.lon, function(address) {
markerObj.setTitle(address);
});
markerObj.addListener('mouseover', function() {
let daily = point.daily || {};
let description = getWeatherDescription(point.current_weather.weathercode);
let content = `<div class="p-2">
<h6>${description}</h6>
<p>Temperature: ${point.current_weather.temperature}°C</p>
<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>
<p>Pressure: ${point.current_weather.surface_pressure || 'No data'} hPa</p>
<p>Soil Moisture: ${point.current_weather.soil_moisture || 'No data'}</p>
<p>Humidity: ${point.current_weather.relativehumidity || 'No data'}%</p>
<p>Wind Speed: ${point.current_weather.windspeed || 'No data'} km/h</p>
<p>UV Index: ${point.current_weather.uv_index || 'No data'}</p>
<p>Cloud Cover: ${point.current_weather.cloudcover || 'No data'}%</p>
<p>Sunrise: ${daily.sunrise ? formatTime(daily.sunrise[0]) : 'No data'}, Sunset: ${daily.sunset ? formatTime(daily.sunset[0]) : 'No data'}</p>
</div>`;
if (infoWindow) infoWindow.close();
infoWindow = new google.maps.InfoWindow({ content: content });
infoWindow.open(map, markerObj);
});
markerObj.addListener('mouseout', function() {
if (infoWindow) infoWindow.close();
});
markers.push(markerObj);
});
})
.catch(err => console.error("Error fetching surrounding weather:", err));
} else {
document.getElementById('surroundingWeatherTable').innerHTML = '';
markers.forEach(marker => marker.setMap(null));
markers = [];
}
// Fetch disaster events using the Ambee API
fetch(`/get_india_disaster_events`)
.then(response => response.json())
.then(data => {
console.log('Ambee disaster events data:', data);
updateDisasterEventsUI(data);
})
.catch(err => console.error("Error fetching disaster events:", err));
// Fetch soil properties and update soil classification
updateSoilAnalysis();
updateSoilType();
showLoading(false);
}
function updateCenterWeatherUI(data) {
const container = document.getElementById('centerWeatherContent');
container.innerHTML = '';
if (data.current_conditions) {
let iconURL = getWeatherIcon(data.current_conditions.weathercode);
let description = getWeatherDescription(data.current_conditions.weathercode);
let daily = data.daily || {};
let sunrise = daily.sunrise ? formatTime(daily.sunrise[0]) : 'No data';
let sunset = daily.sunset ? formatTime(daily.sunset[0]) : 'No data';
container.innerHTML = `<div class="col-12 weather-card p-3 border">
<div class="d-flex align-items-center">
<img src="${iconURL}" alt="weather icon" class="me-3"/>
<div>
<h6>${description}</h6>
<p>Temperature: ${data.current_conditions.temperature}°C</p>
<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>
</div>
</div>
<p>Humidity: ${data.current_conditions.humidity || 'No data'}%</p>
<p>Precipitation: ${daily.precipitation_sum ? daily.precipitation_sum[0] + " mm" : 'No data'}</p>
<p>Cloud Cover: ${data.current_conditions.cloudcover || 'No data'}%</p>
<p>UV Index: ${data.current_conditions.uv_index || 'No data'}</p>
<p>Soil Moisture: ${data.current_conditions.soil_moisture || 'No data'}</p>
<p>Sunrise: ${sunrise}, Sunset: ${sunset}</p>
</div>`;
} else {
container.innerHTML = `<div class="col-12">No data available.</div>`;
}
}
function updateSurroundingWeatherTable(data) {
let container = document.getElementById('surroundingWeatherTable');
let promises = data.map(point => {
return new Promise(resolve => {
reverseGeocode(point.location.lat, point.location.lon, function(address) {
resolve({ point: point, address: address });
});
});
});
Promise.all(promises).then(results => {
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>';
results.forEach(item => {
let pt = item.point;
html += `<tr>
<td>${item.address}</td>
<td>${pt.location.lat.toFixed(4)}, ${pt.location.lon.toFixed(4)}</td>
<td>${pt.current_weather.temperature || 'No data'}</td>
<td>${pt.current_weather.relativehumidity || 'No data'}</td>
<td>${pt.current_weather.surface_pressure || 'No data'}</td>
<td>${pt.current_weather.cloudcover || 'No data'}</td>
</tr>`;
});
html += '</tbody></table>';
container.innerHTML = html;
});
}
// Updated icon selection function that accepts both event type and event name
function getDisasterIcon(eventType, eventName) {
// Use eventName in lowercase; if it's empty, fall back to eventType
let name = eventName ? eventName.toLowerCase() : "";
if (!name && eventType) {
name = eventType.toLowerCase();
}
if (/\bflood\b/.test(name)) {
return "https://img.icons8.com/emoji/48/000000/water-wave.png";
}
if (/\b(thunderstorm|thunder)\b/.test(name)) {
return "https://img.icons8.com/emoji/48/000000/cloud-with-lightning.png";
}
if (/\btornado\b/.test(name)) {
return "https://img.icons8.com/emoji/48/000000/tornado-emoji.png";
}
if (/\b(cyclone|storm)\b/.test(name)) {
return "https://img.icons8.com/emoji/48/000000/cloud-with-lightning.png";
}
if (/\b(wildfire|fire)\b/.test(name)) {
return "https://img.icons8.com/emoji/48/000000/fire.png";
}
if (/\bearthquake\b/.test(name)) {
return "https://img.icons8.com/emoji/48/000000/earthquake.png";
}
return "https://img.icons8.com/emoji/48/000000/exclamation-mark-emoji.png";
}
function updateDisasterEventsUI(data) {
const container = document.getElementById('disasterEventsContent');
container.innerHTML = '';
// Clear existing markers from map
disasterMarkers.forEach(marker => marker.setMap(null));
disasterMarkers = [];
// Use "result" as per the Ambee API response
if (!data.result || !data.result.length) {
container.innerHTML = `<div class="col-12">No disaster events available.</div>`;
return;
}
// Filter unique events using event_name and date as a unique key
const uniqueMap = {};
const uniqueEvents = [];
data.result.forEach(event => {
const key = (event.event_name || '') + '|' + (event.date || '');
if (!uniqueMap[key]) {
uniqueMap[key] = true;
uniqueEvents.push(event);
}
});
// Build HTML table for disaster events
let tableHTML = `<table class="table table-bordered">
<thead>
<tr>
<th>Date</th>
<th>Event Name</th>
<th>Event Type</th>
<th>Location (Lat, Lng)</th>
</tr>
</thead>
<tbody>`;
uniqueEvents.forEach(event => {
const eventDate = event.date || 'N/A';
const eventName = event.event_name || 'No Title';
// Use provided event_type if available, otherwise "UNKNOWN"
const eventType = event.event_type ? event.event_type.toUpperCase() : "UNKNOWN";
const lat = event.lat;
const lng = event.lng;
const locationStr = (lat && lng) ? `${parseFloat(lat).toFixed(4)}, ${parseFloat(lng).toFixed(4)}` : 'N/A';
tableHTML += `<tr>
<td>${eventDate}</td>
<td>${eventName}</td>
<td>${eventType}</td>
<td>${locationStr}</td>
</tr>`;
// Place a marker on the map if location is available
if (lat && lng) {
const pos = { lat: parseFloat(lat), lng: parseFloat(lng) };
// Pass both eventType and eventName for icon selection
const iconURL = getDisasterIcon(eventType, eventName);
const marker = new google.maps.Marker({
position: pos,
map: map,
icon: {
url: iconURL,
scaledSize: new google.maps.Size(40, 40)
}
});
marker.addListener('mouseover', () => {
if (infoWindow) infoWindow.close();
infoWindow = new google.maps.InfoWindow({
content: `<div class="p-2">
<h6>${eventName}</h6>
<p><strong>Date:</strong> ${eventDate}</p>
<p><strong>Type:</strong> ${eventType}</p>
<p><strong>Location:</strong> ${locationStr}</p>
</div>`
});
infoWindow.open(map, marker);
});
marker.addListener('mouseout', () => {
if (infoWindow) infoWindow.close();
});
disasterMarkers.push(marker);
}
});
tableHTML += `</tbody></table>`;
container.innerHTML = tableHTML;
}
function updateSoilAnalysis() {
let centerForSoil;
if (polygon) {
let bounds = new google.maps.LatLngBounds();
polygon.getPath().forEach(latlng => bounds.extend(latlng));
centerForSoil = bounds.getCenter();
} else {
centerForSoil = map.getCenter();
}
let lat = centerForSoil.lat();
let lng = centerForSoil.lng();
fetch(`/get_soil_properties?lat=${lat}&lon=${lng}`)
.then(response => response.json())
.then(data => {
const container = document.getElementById('soilAnalysisContent');
if (data.soil_properties && data.soil_properties.length > 0) {
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>';
data.soil_properties.forEach(item => {
html += `<tr><td>${item[0]}</td><td>${item[1]}</td><td>${item[2]}</td></tr>`;
});
html += '</tbody></table></div>';
container.innerHTML = html;
} else {
container.innerHTML = 'No soil properties data available.';
}
})
.catch(err => console.error("Error fetching soil properties:", err));
}
function updateSoilType() {
let centerForSoil;
if (polygon) {
let bounds = new google.maps.LatLngBounds();
polygon.getPath().forEach(latlng => bounds.extend(latlng));
centerForSoil = bounds.getCenter();
} else {
centerForSoil = map.getCenter();
}
let lat = centerForSoil.lat();
let lng = centerForSoil.lng();
fetch(`/get_soil_classification?lat=${lat}&lon=${lng}`)
.then(response => response.json())
.then(data => {
updateSoilTypeUI(data);
if (soilMarker) {
soilMarker.setMap(null);
soilMarker = null;
}
if (data.soil_type && data.soil_category !== "unknown") {
let iconURL = getSoilIcon(data.soil_category);
let soilMarkerPosition = centerForSoil;
if (polygon) {
soilMarkerPosition = new google.maps.LatLng(
centerForSoil.lat() + 0.03,
centerForSoil.lng() + 0.03
);
}
soilMarker = new google.maps.Marker({
position: soilMarkerPosition,
map: map,
icon: {
url: iconURL,
scaledSize: new google.maps.Size(40, 40)
}
});
soilMarker.addListener('mouseover', function() {
if (infoWindow) infoWindow.close();
infoWindow = new google.maps.InfoWindow({
content: `<div class="p-2"><h6>Soil Classification</h6><p>${data.soil_type} (${data.soil_category})</p></div>`
});
infoWindow.open(map, soilMarker);
});
soilMarker.addListener('mouseout', function() {
if (infoWindow) infoWindow.close();
});
}
})
.catch(err => console.error("Error fetching soil classification:", err));
}
function updateSoilTypeUI(data) {
let container = document.getElementById('soilTypeContent');
if (data.soil_type && data.soil_category !== "unknown") {
let html = `<div class="card p-3">
<h6>Soil Classification</h6>
<p>Dominant Soil Type: ${data.soil_type} (${data.soil_category})</p>`;
if (data.soil_probabilities && data.soil_probabilities.length > 0) {
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>`;
data.soil_probabilities.forEach(prob => {
html += `<tr><td>${prob[0]}</td><td>${prob[1]}</td></tr>`;
});
html += '</tbody></table></div>';
}
html += `</div>`;
container.innerHTML = html;
} else {
container.innerHTML = 'No soil classification data available.';
}
}
function getSoilIcon(soil_category) {
if (soil_category === "black") return "https://img.icons8.com/emoji/48/000000/black-diamond-emoji.png";
else if (soil_category === "red") return "https://img.icons8.com/emoji/48/ff0000/red-diamond-emoji.png";
else if (soil_category === "alluvial") return "https://img.icons8.com/emoji/48/008000/green-diamond-emoji.png";
else if (soil_category === "desert") return "https://img.icons8.com/emoji/48/8b4513/brown-diamond-emoji.png";
return "";
}
function showLoading(show) {
document.getElementById('loadingOverlay').style.display = show ? 'flex' : 'none';
}
function showToast(message) {
Toastify({
text: message,
duration: 3000,
gravity: "top",
position: "right",
backgroundColor: "#333",
stopOnFocus: true
}).showToast();
}
window.onload = initMap;
</script>
</body>
</html>