Spaces:
Running
Running
<html lang="en"> | |
<head> | |
<meta charset="UTF-8" /> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> | |
<title>Farm Geofencing & AI Design</title> | |
<!-- External CSS --> | |
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/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); | |
} | |
.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">Farm Geofencing & AI Design</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"> | |
<!-- Controls Panel --> | |
<div class="col-md-3"> | |
<!-- Search Location (Optional - keeping for map centering) --> | |
<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> | |
<!-- Farm Design Inputs --> | |
<div class="control-panel mb-4"> | |
<h5 class="mb-3">AI Farm Design</h5> | |
<div class="mb-3"> | |
<label for="farmTypeSelect" class="form-label">Farming Type</label> | |
<select id="farmTypeSelect" class="form-select"> | |
<option value="Horticulture">Horticulture (Fruits, Vegetables)</option> | |
<option value="Plantation">Plantation (Trees, Coffee, Tea)</option> | |
<option value="Poultry">Poultry (Chickens, Ducks)</option> | |
<option value="Dairy">Dairy (Cows, Goats)</option> | |
<option value="Mixed Farming">Mixed Farming</option> | |
</select> | |
</div> | |
<div class="mb-3"> | |
<label for="preferencesInput" class="form-label">Additional Preferences</label> | |
<textarea id="preferencesInput" class="form-control" rows="3" placeholder="e.g., organic methods, specific crop types, number of animals, water source location..."></textarea> | |
</div> | |
<button id="generateDesignBtn" class="btn btn-info w-100"> | |
<i class="fas fa-magic"></i> Generate Farm Design | |
</button> | |
</div> | |
</div> | |
<!-- Map & Geofence 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="#geofenceInfo">Geofence Info</a> | |
</li> | |
<li class="nav-item"> | |
<a class="nav-link" data-bs-toggle="tab" href="#farmDesignOutput">AI Farm Design</a> | |
</li> | |
</ul> | |
<div class="tab-content mt-3"> | |
<div class="tab-pane fade show active" id="geofenceInfo"> | |
<div id="geofenceInfoContent"> | |
<p>Draw a polygon on the map to see its area and coordinates.</p> | |
</div> | |
</div> | |
<div class="tab-pane fade" id="farmDesignOutput"> | |
<div id="farmDesignContent"> | |
<p>Click "Generate Farm Design" after drawing a geofence and selecting your farming type.</p> | |
</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 (Geofence)</h6> | |
<ol> | |
<li>Click "Start Drawing" to activate polygon drawing mode.</li> | |
<li>Click on the map to add vertices for your farm's boundary.</li> | |
<li>Double-click (or click the first point you drew) to complete the polygon.</li> | |
<li>Once drawn, the farm's area and coordinates will appear in the "Geofence Info" tab.</li> | |
<li>Use "Clear" to remove the drawn area and its data.</li> | |
<li>Check "Snap to Grid" to align polygon points to a grid for cleaner shapes.</li> | |
</ol> | |
<h6>AI Farm Design</h6> | |
<ol> | |
<li>First, draw your farm's geofence on the map.</li> | |
<li>Select your primary farming type (e.g., Horticulture, Poultry) from the dropdown.</li> | |
<li>Enter any additional preferences or specific requirements in the text area.</li> | |
<li>Click "Generate Farm Design". The AI will analyze your farm's location (via a satellite image of the geofenced area) and your input to provide a detailed textual design plan.</li> | |
<li>The AI's design plan will appear in the "AI Farm Design" tab. This plan includes layout recommendations and a descriptive prompt for how an image generation AI could visualize the design.</li> | |
</ol> | |
<h6>Searching Locations</h6> | |
<ul> | |
<li>Enter an address or latitude/longitude coordinates and click "Search" to center the map.</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/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script> | |
<script src="https://kit.fontawesome.com/your-fontawesome-kit.js"></script> <!-- Ensure this points to your Font Awesome kit --> | |
<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; | |
let geocoder; | |
function initMap() { | |
map = new google.maps.Map(document.getElementById('map'), { | |
center: { lat: 20.5937, lng: 78.9629 }, // Default to India | |
zoom: 5, | |
mapTypeControl: true, | |
fullscreenControl: true, | |
streetViewControl: true, | |
streetViewControlOptions: { | |
position: google.maps.ControlPosition.RIGHT_BOTTOM | |
} | |
}); | |
geocoder = new google.maps.Geocoder(); | |
setupDrawingManager(); | |
setupEventListeners(); | |
} | |
// 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 { | |
// Clear existing polygon before drawing a new one | |
clearPolygon(); | |
drawingManager.setDrawingMode(google.maps.drawing.OverlayType.POLYGON); | |
this.textContent = "Stop Drawing"; | |
} | |
}); | |
function setupDrawingManager() { | |
drawingManager = new google.maps.drawing.DrawingManager({ | |
drawingMode: null, // Start with no drawing mode enabled | |
drawingControl: false, // Don't show the default drawing controls | |
polygonOptions: { | |
fillColor: '#4CAF50', | |
fillOpacity: 0.3, | |
strokeWeight: 2, | |
strokeColor: '#4CAF50', | |
editable: true // Allow editing after drawing | |
} | |
}); | |
drawingManager.setMap(map); | |
// Event listener for when a polygon is completed | |
google.maps.event.addListener(drawingManager, 'polygoncomplete', function(poly) { | |
// If there was an old polygon, clear it | |
if (polygon) { | |
polygon.setMap(null); | |
} | |
polygon = poly; | |
drawingManager.setDrawingMode(null); // Exit drawing mode | |
document.getElementById('startDrawingBtn').textContent = "Start Drawing"; | |
if (document.getElementById('snapToGridCheck').checked) { | |
snapPolygonToGrid(polygon); | |
} | |
updateGeofenceData(); | |
// Add listener for polygon path changes (for editing) | |
google.maps.event.addListener(polygon.getPath(), 'set_at', updateGeofenceData); | |
google.maps.event.addListener(polygon.getPath(), 'insert_at', updateGeofenceData); | |
}); | |
} | |
function snapPolygonToGrid(poly) { | |
const gridSize = 0.0005; // Finer grid for more precision | |
let path = poly.getPath(); | |
for (let i = 0; i < path.getLength(); i++) { | |
let pt = path.getAt(i); | |
let snappedLat = Math.round(pt.lat() / gridSize) * gridSize; | |
let snappedLng = Math.round(pt.lng() / gridSize) * gridSize; | |
path.setAt(i, new google.maps.LatLng(snappedLat, snappedLng)); | |
} | |
} | |
function setupEventListeners() { | |
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 }); // Reset to India | |
map.setZoom(5); | |
}); | |
document.getElementById('addressSearchBtn').addEventListener('click', searchByAddress); | |
document.getElementById('coordSearchBtn').addEventListener('click', searchByCoordinates); | |
document.getElementById('snapToGridCheck').addEventListener('change', function() { | |
if (polygon && this.checked) { | |
snapPolygonToGrid(polygon); | |
updateGeofenceData(); // Recalculate area after snapping | |
} | |
}); | |
document.getElementById('generateDesignBtn').addEventListener('click', generateFarmDesign); | |
} | |
function clearPolygon() { | |
if (polygon) { | |
polygon.setMap(null); | |
polygon = null; | |
} | |
document.getElementById('geofenceInfoContent').innerHTML = '<p>Draw a polygon on the map to see its area and coordinates.</p>'; | |
document.getElementById('farmDesignContent').innerHTML = '<p>Click "Generate Farm Design" after drawing a geofence and selecting your farming type.</p>'; | |
} | |
function clearAll() { | |
clearPolygon(); | |
// Reset drawing mode if it was active | |
drawingManager.setDrawingMode(null); | |
document.getElementById('startDrawingBtn').textContent = "Start Drawing"; | |
} | |
function searchByAddress() { | |
let address = document.getElementById('addressInput').value; | |
if (!address) { | |
showToast("Please enter an address"); | |
return; | |
} | |
geocoder.geocode({ address: address }, function(results, status) { | |
if (status === google.maps.GeocoderStatus.OK) { | |
let location = results[0].geometry.location; | |
map.setCenter(location); | |
map.setZoom(12); | |
} 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) && lat >= -90 && lat <= 90 && lng >= -180 && lng <= 180) { | |
map.setCenter({ lat: lat, lng: lng }); | |
map.setZoom(12); // Zoom in on the specified coordinates | |
} else { | |
showToast("Invalid coordinates. Latitude must be between -90 and 90, Longitude between -180 and 180."); | |
} | |
} | |
function updateGeofenceData() { | |
if (!polygon) { | |
document.getElementById('geofenceInfoContent').innerHTML = '<p>Draw a polygon on the map to see its area and coordinates.</p>'; | |
return; | |
} | |
showLoading(true); | |
const path = polygon.getPath(); | |
const coordinates = []; | |
path.forEach(function(latLng) { | |
coordinates.push({ lat: latLng.lat(), lng: latLng.lng() }); | |
}); | |
fetch('/calculate_area', { | |
method: 'POST', | |
headers: { | |
'Content-Type': 'application/json', | |
}, | |
body: JSON.stringify({ coordinates: coordinates }), | |
}) | |
.then(response => response.json()) | |
.then(data => { | |
showLoading(false); | |
const container = document.getElementById('geofenceInfoContent'); | |
if (data.error) { | |
container.innerHTML = `<p class="text-danger">Error: ${data.error}</p>`; | |
showToast(`Error: ${data.error}`); | |
return; | |
} | |
let coordsHtml = '<h6>Coordinates:</h6><ul>'; | |
data.coordinates.forEach(coord => { | |
coordsHtml += `<li>Lat: ${coord.lat.toFixed(6)}, Lng: ${coord.lng.toFixed(6)}</li>`; | |
}); | |
coordsHtml += '</ul>'; | |
container.innerHTML = ` | |
<h6>Area:</h6> | |
<ul> | |
<li><strong>${data.area_hectares.toFixed(2)} hectares</strong></li> | |
<li>${data.area_sq_meters.toFixed(2)} sq meters</li> | |
<li>${data.area_sq_km.toFixed(2)} sq km</li> | |
<li>${data.area_acres.toFixed(2)} acres</li> | |
</ul> | |
${coordsHtml} | |
`; | |
}) | |
.catch(error => { | |
console.error('Error fetching area calculation:', error); | |
showLoading(false); | |
document.getElementById('geofenceInfoContent').innerHTML = '<p class="text-danger">Failed to calculate area. Please try again.</p>'; | |
showToast("Failed to calculate area."); | |
}); | |
} | |
function generateFarmDesign() { | |
if (!polygon) { | |
showToast("Please draw a farm area (geofence) on the map first."); | |
return; | |
} | |
showLoading(true); | |
const path = polygon.getPath(); | |
const coordinates = []; | |
path.forEach(function(latLng) { | |
coordinates.push({ lat: latLng.lat(), lng: latLng.lng() }); | |
}); | |
const farmType = document.getElementById('farmTypeSelect').value; | |
const preferences = document.getElementById('preferencesInput').value; | |
fetch('/generate_farm_design', { | |
method: 'POST', | |
headers: { | |
'Content-Type': 'application/json', | |
}, | |
body: JSON.stringify({ | |
coordinates: coordinates, | |
farm_type: farmType, | |
preferences: preferences | |
}), | |
}) | |
.then(response => response.json()) | |
.then(data => { | |
showLoading(false); | |
const container = document.getElementById('farmDesignContent'); | |
if (data.error) { | |
container.innerHTML = `<p class="text-danger">Error: ${data.error}</p>`; | |
showToast(`Error: ${data.error}`); | |
return; | |
} | |
let designHtml = `<h6>AI-Generated Farm Design Plan for ${farmType} Farm:</h6> | |
<div class="card p-3 mb-3"> | |
${data.design_plan.split('\n').map(p => `<p>${p}</p>`).join('')} | |
</div>`; | |
if (data.visual_design_prompt) { | |
designHtml += `<h6 class="mt-4">Prompt for Image Generation AI:</h6> | |
<div class="card p-3 bg-light"> | |
<p class="mb-0"><em>"${data.visual_design_prompt}"</em></p> | |
<small class="text-muted mt-2"> | |
(Use this prompt with a dedicated text-to-image AI (e.g., DALL-E, Midjourney, Stable Diffusion) to visualize the design. | |
The current Google Gemini API provides textual advice and prompts for external image generation.) | |
</small> | |
</div>`; | |
} | |
if (data.map_image_url) { | |
designHtml += `<h6 class="mt-4">Farm Area Context:</h6> | |
<img src="${data.map_image_url}" alt="Farm Area Satellite Image" class="img-fluid rounded shadow-sm mt-2" style="max-width: 600px; border: 1px solid #ddd;"> | |
<small class="text-muted d-block mt-1">Satellite image of the geofenced area used for AI analysis.</small>`; | |
} | |
container.innerHTML = designHtml; | |
showToast("Farm design generated successfully!"); | |
// Switch to the AI Farm Design tab | |
new bootstrap.Tab(document.querySelector('#dataTabs a[href="#farmDesignOutput"]')).show(); | |
}) | |
.catch(error => { | |
console.error('Error generating farm design:', error); | |
showLoading(false); | |
document.getElementById('farmDesignContent').innerHTML = '<p class="text-danger">Failed to generate farm design. Please check console for details and ensure API keys are correct.</p>'; | |
showToast("Failed to generate farm design."); | |
}); | |
} | |
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> |