|
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Soil Analysis Dashboard</title>
|
|
|
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
|
|
|
<link rel="stylesheet" href="https://unpkg.com/leaflet/dist/leaflet.css" />
|
|
<style>
|
|
body { background-color: #f4f8f4; }
|
|
.navbar-brand { font-weight: bold; }
|
|
#map { height: 400px; border-radius: 0.5rem; border: 1px solid #ddd; }
|
|
.card { border: 1px solid #28a745; }
|
|
.btn-success { background-color: #28a745; border-color: #28a745; }
|
|
.btn-success:hover { background-color: #218838; border-color: #1e7e34; }
|
|
.loader { display: none; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<nav class="navbar navbar-dark bg-success">
|
|
<div class="container-fluid">
|
|
<span class="navbar-brand mb-0 h1 mx-auto">🌱 Soil Analysis Dashboard</span>
|
|
</div>
|
|
</nav>
|
|
|
|
<div class="container my-4">
|
|
|
|
<div class="card mb-4">
|
|
<div class="card-header fw-bold">Step 1: Select Location</div>
|
|
<div class="card-body">
|
|
<div class="row">
|
|
<div class="col-lg-8 mb-3 mb-lg-0">
|
|
<div id="map"></div>
|
|
</div>
|
|
<div class="col-lg-4">
|
|
<div class="mb-3">
|
|
<label for="lat" class="form-label">Latitude</label>
|
|
<input type="text" id="lat" class="form-control" placeholder="Click map or enter manually">
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="lon" class="form-label">Longitude</label>
|
|
<input type="text" id="lon" class="form-control" placeholder="Click map or enter manually">
|
|
</div>
|
|
<button id="current-location" class="btn btn-secondary w-100 mb-2">Use Current Location</button>
|
|
<button id="fetch-report" class="btn btn-success w-100">
|
|
<span class="spinner-border spinner-border-sm loader" role="status" aria-hidden="true"></span>
|
|
Fetch Soil Report
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
<div id="alert-container" class="mt-3"></div>
|
|
|
|
|
|
<div id="soil-report-section" class="d-none">
|
|
<div class="card mb-4">
|
|
<div class="card-header fw-bold">Step 2: Soil Report</div>
|
|
<div class="card-body">
|
|
<div class="row">
|
|
<div class="col-md-6" id="classification-report"></div>
|
|
<div class="col-md-6" id="properties-report"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
<div id="analysis-section" class="d-none">
|
|
<div class="card mb-4">
|
|
<div class="card-header fw-bold">Step 3: AI Analysis & Recommendations</div>
|
|
<div class="card-body">
|
|
<div class="row align-items-end">
|
|
<div class="col-md-8 mb-3 mb-md-0">
|
|
<label for="language" class="form-label">Response Language</label>
|
|
<select class="form-select" id="language">
|
|
<option value="English">English</option><option value="Hindi">Hindi</option><option value="Bengali">Bengali</option>
|
|
<option value="Telugu">Telugu</option><option value="Marathi">Marathi</option><option value="Tamil">Tamil</option>
|
|
<option value="Gujarati">Gujarati</option><option value="Urdu">Urdu</option><option value="Kannada">Kannada</option>
|
|
<option value="Odia">Odia</option><option value="Malayalam">Malayalam</option>
|
|
</select>
|
|
</div>
|
|
<div class="col-md-4">
|
|
<button id="analyze-soil" class="btn btn-success w-100">
|
|
<span class="spinner-border spinner-border-sm loader" role="status" aria-hidden="true"></span>
|
|
Analyze with AI
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<hr>
|
|
<div id="analysis-result"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
|
<script src="https://unpkg.com/leaflet/dist/leaflet.js"></script>
|
|
<script>
|
|
|
|
let soilReportData = null;
|
|
let map, marker;
|
|
|
|
|
|
const showAlert = (message, type = 'danger') => {
|
|
const alertContainer = document.getElementById('alert-container');
|
|
alertContainer.innerHTML = `<div class="alert alert-${type} alert-dismissible fade show" role="alert">
|
|
${message}
|
|
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
|
</div>`;
|
|
};
|
|
|
|
const toggleLoader = (buttonId, show) => {
|
|
const button = document.getElementById(buttonId);
|
|
const loader = button.querySelector('.loader');
|
|
button.disabled = show;
|
|
loader.style.display = show ? 'inline-block' : 'none';
|
|
};
|
|
|
|
|
|
const initializeMap = () => {
|
|
map = L.map('map').setView([20.5937, 78.9629], 5);
|
|
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
|
attribution: '© OpenStreetMap contributors'
|
|
}).addTo(map);
|
|
|
|
map.on('click', e => {
|
|
const { lat, lng } = e.latlng;
|
|
document.getElementById('lat').value = lat.toFixed(6);
|
|
document.getElementById('lon').value = lng.toFixed(6);
|
|
if (marker) marker.setLatLng(e.latlng);
|
|
else marker = L.marker(e.latlng).addTo(map);
|
|
});
|
|
};
|
|
|
|
|
|
document.getElementById('current-location').addEventListener('click', () => {
|
|
if (navigator.geolocation) {
|
|
navigator.geolocation.getCurrentPosition(pos => {
|
|
const { latitude, longitude } = pos.coords;
|
|
document.getElementById('lat').value = latitude.toFixed(6);
|
|
document.getElementById('lon').value = longitude.toFixed(6);
|
|
const latlng = L.latLng(latitude, longitude);
|
|
map.setView(latlng, 13);
|
|
if (marker) marker.setLatLng(latlng);
|
|
else marker = L.marker(latlng).addTo(map);
|
|
}, () => showAlert('Could not get your location.'));
|
|
} else {
|
|
showAlert('Geolocation is not supported by your browser.');
|
|
}
|
|
});
|
|
|
|
document.getElementById('fetch-report').addEventListener('click', () => {
|
|
const lat = document.getElementById('lat').value;
|
|
const lon = document.getElementById('lon').value;
|
|
if (!lat || !lon) return showAlert('Please provide latitude and longitude.');
|
|
|
|
toggleLoader('fetch-report', true);
|
|
fetch('/get_soil_report', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ lat, lon })
|
|
})
|
|
.then(response => response.json().then(data => ({ ok: response.ok, data })))
|
|
.then(({ ok, data }) => {
|
|
if (!ok) throw new Error(data.error || 'Unknown error occurred.');
|
|
soilReportData = data;
|
|
renderSoilReport(data);
|
|
document.getElementById('soil-report-section').classList.remove('d-none');
|
|
document.getElementById('analysis-section').classList.remove('d-none');
|
|
})
|
|
.catch(err => showAlert(`Error fetching soil report: ${err.message}`))
|
|
.finally(() => toggleLoader('fetch-report', false));
|
|
});
|
|
|
|
document.getElementById('analyze-soil').addEventListener('click', () => {
|
|
if (!soilReportData) return showAlert('Please fetch a soil report first.');
|
|
|
|
toggleLoader('analyze-soil', true);
|
|
document.getElementById('analysis-result').innerHTML = '';
|
|
|
|
|
|
fetch('/analyze_soil', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({
|
|
soil_report: soilReportData,
|
|
language: document.getElementById('language').value
|
|
})
|
|
})
|
|
.then(response => response.json().then(data => ({ ok: response.ok, data })))
|
|
.then(({ ok, data }) => {
|
|
if (!ok) throw new Error(data.error || 'Failed to get analysis.');
|
|
renderAnalysis(data);
|
|
})
|
|
.catch(err => showAlert(`Error during analysis: ${err.message}`))
|
|
.finally(() => toggleLoader('analyze-soil', false));
|
|
});
|
|
|
|
|
|
const renderSoilReport = (data) => {
|
|
let classHtml = `<h5>Soil Classification</h5><p><strong>Type:</strong> ${data.classification.soil_type}</p>`;
|
|
if (data.classification.probabilities.length) {
|
|
classHtml += `<table class="table table-sm table-bordered"><thead><tr><th>Type</th><th>Probability</th></tr></thead><tbody>`;
|
|
data.classification.probabilities.forEach(([type, prob]) => {
|
|
classHtml += `<tr><td>${type}</td><td>${prob}%</td></tr>`;
|
|
});
|
|
classHtml += `</tbody></table>`;
|
|
}
|
|
document.getElementById('classification-report').innerHTML = classHtml;
|
|
|
|
let propHtml = `<h5>Soil Properties (5-15cm)</h5><table class="table table-sm table-striped"><thead><tr><th>Parameter</th><th>Value</th></tr></thead><tbody>`;
|
|
data.properties.forEach(({ parameter, value, unit }) => {
|
|
const displayValue = (typeof value === 'number') ? `${value.toFixed(2)} ${unit}` : 'N/A';
|
|
propHtml += `<tr><td>${parameter}</td><td>${displayValue}</td></tr>`;
|
|
});
|
|
propHtml += `</tbody></table>`;
|
|
document.getElementById('properties-report').innerHTML = propHtml;
|
|
};
|
|
|
|
const renderAnalysis = (data) => {
|
|
let analysisHtml = `
|
|
<div class="mb-4">
|
|
<h4 class="text-success">${data.soilType}</h4>
|
|
<ul class="list-unstyled">
|
|
${data.generalInsights.map(insight => `<li>- ${insight}</li>`).join('')}
|
|
</ul>
|
|
</div>
|
|
|
|
<h5 class="mt-4">Parameter Analysis</h5>
|
|
<table class="table table-bordered">
|
|
<thead class="table-light"><tr><th>Parameter</th><th>Value</th><th>Range</th><th>Comment</th></tr></thead>
|
|
<tbody>
|
|
${data.parameters.map(p => `<tr><td>${p.parameter}</td><td>${p.value}</td><td>${p.range}</td><td>${p.comment}</td></tr>`).join('')}
|
|
</tbody>
|
|
</table>
|
|
|
|
<h5 class="mt-4">Crop Recommendations</h5>
|
|
<table class="table table-striped">
|
|
<thead class="table-light"><tr><th>Crop</th><th>Reason</th></tr></thead>
|
|
<tbody>
|
|
${data.cropRecommendations.map(c => `<tr><td>${c.crop}</td><td>${c.reason}</td></tr>`).join('')}
|
|
</tbody>
|
|
</table>
|
|
|
|
<h5 class="mt-4">Management Recommendations</h5>
|
|
<p><strong>Fertilization:</strong> ${data.managementRecommendations.fertilization}</p>
|
|
<p><strong>Irrigation:</strong> ${data.managementRecommendations.irrigation}</p>
|
|
`;
|
|
document.getElementById('analysis-result').innerHTML = analysisHtml;
|
|
};
|
|
|
|
|
|
document.addEventListener('DOMContentLoaded', initializeMap);
|
|
</script>
|
|
</body>
|
|
</html> |