Spaces:
Sleeping
Sleeping
Upload 2 files
Browse files- ai_studio_code.html +503 -0
- ai_studio_code.py +206 -0
ai_studio_code.html
ADDED
|
@@ -0,0 +1,503 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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>Farm Geofencing & AI Design</title>
|
| 7 |
+
<!-- External CSS -->
|
| 8 |
+
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/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 |
+
.loading-overlay {
|
| 25 |
+
position: fixed;
|
| 26 |
+
top: 0;
|
| 27 |
+
left: 0;
|
| 28 |
+
width: 100%;
|
| 29 |
+
height: 100%;
|
| 30 |
+
background: rgba(255,255,255,0.8);
|
| 31 |
+
display: none;
|
| 32 |
+
justify-content: center;
|
| 33 |
+
align-items: center;
|
| 34 |
+
z-index: 9999;
|
| 35 |
+
}
|
| 36 |
+
.spinner {
|
| 37 |
+
width: 50px;
|
| 38 |
+
height: 50px;
|
| 39 |
+
border: 5px solid #f3f3f3;
|
| 40 |
+
border-top: 5px solid #3498db;
|
| 41 |
+
border-radius: 50%;
|
| 42 |
+
animation: spin 1s linear infinite;
|
| 43 |
+
}
|
| 44 |
+
@keyframes spin {
|
| 45 |
+
0% { transform: rotate(0deg); }
|
| 46 |
+
100% { transform: rotate(360deg); }
|
| 47 |
+
}
|
| 48 |
+
</style>
|
| 49 |
+
</head>
|
| 50 |
+
<body class="bg-gray-100">
|
| 51 |
+
<!-- Loading Overlay -->
|
| 52 |
+
<div id="loadingOverlay" class="loading-overlay">
|
| 53 |
+
<div class="spinner"></div>
|
| 54 |
+
</div>
|
| 55 |
+
<div class="container-fluid py-4">
|
| 56 |
+
<div class="row mb-4">
|
| 57 |
+
<div class="col-12">
|
| 58 |
+
<div class="d-flex justify-content-between align-items-center mb-4">
|
| 59 |
+
<h1 class="text-3xl font-bold">Farm Geofencing & AI Design</h1>
|
| 60 |
+
<div class="d-flex gap-2">
|
| 61 |
+
<button id="helpBtn" class="btn btn-info">
|
| 62 |
+
<i class="fas fa-question-circle"></i> Help
|
| 63 |
+
</button>
|
| 64 |
+
<button id="resetBtn" class="btn btn-warning">
|
| 65 |
+
<i class="fas fa-redo"></i> Reset
|
| 66 |
+
</button>
|
| 67 |
+
</div>
|
| 68 |
+
</div>
|
| 69 |
+
</div>
|
| 70 |
+
</div>
|
| 71 |
+
<div class="row">
|
| 72 |
+
<!-- Controls Panel -->
|
| 73 |
+
<div class="col-md-3">
|
| 74 |
+
<!-- Search Location (Optional - keeping for map centering) -->
|
| 75 |
+
<div class="control-panel mb-4">
|
| 76 |
+
<h5 class="mb-3">Search Location</h5>
|
| 77 |
+
<div class="mb-3">
|
| 78 |
+
<input type="text" id="addressInput" class="form-control mb-2" placeholder="Enter Address">
|
| 79 |
+
<button id="addressSearchBtn" class="btn btn-primary w-100">
|
| 80 |
+
<i class="fas fa-search"></i> Search Address
|
| 81 |
+
</button>
|
| 82 |
+
</div>
|
| 83 |
+
<div class="mb-3">
|
| 84 |
+
<div class="input-group mb-2">
|
| 85 |
+
<input type="number" id="latInput" class="form-control" placeholder="Latitude" step="0.000001">
|
| 86 |
+
<input type="number" id="lngInput" class="form-control" placeholder="Longitude" step="0.000001">
|
| 87 |
+
</div>
|
| 88 |
+
<button id="coordSearchBtn" class="btn btn-primary w-100">
|
| 89 |
+
<i class="fas fa-map-marker-alt"></i> Search Coordinates
|
| 90 |
+
</button>
|
| 91 |
+
</div>
|
| 92 |
+
</div>
|
| 93 |
+
<!-- Drawing Controls -->
|
| 94 |
+
<div class="control-panel mb-4">
|
| 95 |
+
<h5 class="mb-3">Drawing Tools</h5>
|
| 96 |
+
<div class="btn-group w-100 mb-2">
|
| 97 |
+
<button id="startDrawingBtn" class="btn btn-success">Start Drawing</button>
|
| 98 |
+
<button id="clearDrawingBtn" class="btn btn-danger">Clear</button>
|
| 99 |
+
</div>
|
| 100 |
+
<div class="form-check mt-2">
|
| 101 |
+
<input class="form-check-input" type="checkbox" id="snapToGridCheck">
|
| 102 |
+
<label class="form-check-label" for="snapToGridCheck">Snap to Grid</label>
|
| 103 |
+
</div>
|
| 104 |
+
</div>
|
| 105 |
+
<!-- Farm Design Inputs -->
|
| 106 |
+
<div class="control-panel mb-4">
|
| 107 |
+
<h5 class="mb-3">AI Farm Design</h5>
|
| 108 |
+
<div class="mb-3">
|
| 109 |
+
<label for="farmTypeSelect" class="form-label">Farming Type</label>
|
| 110 |
+
<select id="farmTypeSelect" class="form-select">
|
| 111 |
+
<option value="Horticulture">Horticulture (Fruits, Vegetables)</option>
|
| 112 |
+
<option value="Plantation">Plantation (Trees, Coffee, Tea)</option>
|
| 113 |
+
<option value="Poultry">Poultry (Chickens, Ducks)</option>
|
| 114 |
+
<option value="Dairy">Dairy (Cows, Goats)</option>
|
| 115 |
+
<option value="Mixed Farming">Mixed Farming</option>
|
| 116 |
+
</select>
|
| 117 |
+
</div>
|
| 118 |
+
<div class="mb-3">
|
| 119 |
+
<label for="preferencesInput" class="form-label">Additional Preferences</label>
|
| 120 |
+
<textarea id="preferencesInput" class="form-control" rows="3" placeholder="e.g., organic methods, specific crop types, number of animals, water source location..."></textarea>
|
| 121 |
+
</div>
|
| 122 |
+
<button id="generateDesignBtn" class="btn btn-info w-100">
|
| 123 |
+
<i class="fas fa-magic"></i> Generate Farm Design
|
| 124 |
+
</button>
|
| 125 |
+
</div>
|
| 126 |
+
</div>
|
| 127 |
+
<!-- Map & Geofence Data Display -->
|
| 128 |
+
<div class="col-md-9">
|
| 129 |
+
<div class="row">
|
| 130 |
+
<div class="col-12">
|
| 131 |
+
<div id="map"></div>
|
| 132 |
+
</div>
|
| 133 |
+
</div>
|
| 134 |
+
<!-- Tabbed Data Display -->
|
| 135 |
+
<div class="row mt-4">
|
| 136 |
+
<div class="col-12">
|
| 137 |
+
<div class="card">
|
| 138 |
+
<div class="card-body">
|
| 139 |
+
<ul class="nav nav-tabs" id="dataTabs">
|
| 140 |
+
<li class="nav-item">
|
| 141 |
+
<a class="nav-link active" data-bs-toggle="tab" href="#geofenceInfo">Geofence Info</a>
|
| 142 |
+
</li>
|
| 143 |
+
<li class="nav-item">
|
| 144 |
+
<a class="nav-link" data-bs-toggle="tab" href="#farmDesignOutput">AI Farm Design</a>
|
| 145 |
+
</li>
|
| 146 |
+
</ul>
|
| 147 |
+
<div class="tab-content mt-3">
|
| 148 |
+
<div class="tab-pane fade show active" id="geofenceInfo">
|
| 149 |
+
<div id="geofenceInfoContent">
|
| 150 |
+
<p>Draw a polygon on the map to see its area and coordinates.</p>
|
| 151 |
+
</div>
|
| 152 |
+
</div>
|
| 153 |
+
<div class="tab-pane fade" id="farmDesignOutput">
|
| 154 |
+
<div id="farmDesignContent">
|
| 155 |
+
<p>Click "Generate Farm Design" after drawing a geofence and selecting your farming type.</p>
|
| 156 |
+
</div>
|
| 157 |
+
</div>
|
| 158 |
+
</div>
|
| 159 |
+
</div>
|
| 160 |
+
</div>
|
| 161 |
+
</div>
|
| 162 |
+
</div>
|
| 163 |
+
</div>
|
| 164 |
+
</div>
|
| 165 |
+
</div>
|
| 166 |
+
|
| 167 |
+
<!-- Help Modal -->
|
| 168 |
+
<div class="modal fade" id="helpModal" tabindex="-1">
|
| 169 |
+
<div class="modal-dialog">
|
| 170 |
+
<div class="modal-content">
|
| 171 |
+
<div class="modal-header">
|
| 172 |
+
<h5 class="modal-title">How to Use the Dashboard</h5>
|
| 173 |
+
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
| 174 |
+
</div>
|
| 175 |
+
<div class="modal-body">
|
| 176 |
+
<h6>Drawing a Farm Area (Geofence)</h6>
|
| 177 |
+
<ol>
|
| 178 |
+
<li>Click "Start Drawing" to activate polygon drawing mode.</li>
|
| 179 |
+
<li>Click on the map to add vertices for your farm's boundary.</li>
|
| 180 |
+
<li>Double-click (or click the first point you drew) to complete the polygon.</li>
|
| 181 |
+
<li>Once drawn, the farm's area and coordinates will appear in the "Geofence Info" tab.</li>
|
| 182 |
+
<li>Use "Clear" to remove the drawn area and its data.</li>
|
| 183 |
+
<li>Check "Snap to Grid" to align polygon points to a grid for cleaner shapes.</li>
|
| 184 |
+
</ol>
|
| 185 |
+
<h6>AI Farm Design</h6>
|
| 186 |
+
<ol>
|
| 187 |
+
<li>First, draw your farm's geofence on the map.</li>
|
| 188 |
+
<li>Select your primary farming type (e.g., Horticulture, Poultry) from the dropdown.</li>
|
| 189 |
+
<li>Enter any additional preferences or specific requirements in the text area.</li>
|
| 190 |
+
<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>
|
| 191 |
+
<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>
|
| 192 |
+
</ol>
|
| 193 |
+
<h6>Searching Locations</h6>
|
| 194 |
+
<ul>
|
| 195 |
+
<li>Enter an address or latitude/longitude coordinates and click "Search" to center the map.</li>
|
| 196 |
+
</ul>
|
| 197 |
+
</div>
|
| 198 |
+
</div>
|
| 199 |
+
</div>
|
| 200 |
+
</div>
|
| 201 |
+
|
| 202 |
+
<!-- External Scripts -->
|
| 203 |
+
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
| 204 |
+
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
|
| 205 |
+
<script src="https://kit.fontawesome.com/your-fontawesome-kit.js"></script> <!-- Ensure this points to your Font Awesome kit -->
|
| 206 |
+
<script src="https://cdn.jsdelivr.net/npm/toastify-js"></script>
|
| 207 |
+
<script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyBvVLjWmCja331H8SuIZ4UlJdZytuYkC6Y&libraries=drawing,places"></script>
|
| 208 |
+
|
| 209 |
+
<!-- Custom JavaScript -->
|
| 210 |
+
<script>
|
| 211 |
+
let map, drawingManager, polygon;
|
| 212 |
+
let geocoder;
|
| 213 |
+
|
| 214 |
+
function initMap() {
|
| 215 |
+
map = new google.maps.Map(document.getElementById('map'), {
|
| 216 |
+
center: { lat: 20.5937, lng: 78.9629 }, // Default to India
|
| 217 |
+
zoom: 5,
|
| 218 |
+
mapTypeControl: true,
|
| 219 |
+
fullscreenControl: true,
|
| 220 |
+
streetViewControl: true,
|
| 221 |
+
streetViewControlOptions: {
|
| 222 |
+
position: google.maps.ControlPosition.RIGHT_BOTTOM
|
| 223 |
+
}
|
| 224 |
+
});
|
| 225 |
+
geocoder = new google.maps.Geocoder();
|
| 226 |
+
setupDrawingManager();
|
| 227 |
+
setupEventListeners();
|
| 228 |
+
}
|
| 229 |
+
|
| 230 |
+
// Toggle draw mode on click
|
| 231 |
+
document.getElementById('startDrawingBtn').addEventListener('click', function() {
|
| 232 |
+
if (drawingManager.getDrawingMode() == google.maps.drawing.OverlayType.POLYGON) {
|
| 233 |
+
drawingManager.setDrawingMode(null);
|
| 234 |
+
this.textContent = "Start Drawing";
|
| 235 |
+
} else {
|
| 236 |
+
// Clear existing polygon before drawing a new one
|
| 237 |
+
clearPolygon();
|
| 238 |
+
drawingManager.setDrawingMode(google.maps.drawing.OverlayType.POLYGON);
|
| 239 |
+
this.textContent = "Stop Drawing";
|
| 240 |
+
}
|
| 241 |
+
});
|
| 242 |
+
|
| 243 |
+
function setupDrawingManager() {
|
| 244 |
+
drawingManager = new google.maps.drawing.DrawingManager({
|
| 245 |
+
drawingMode: null, // Start with no drawing mode enabled
|
| 246 |
+
drawingControl: false, // Don't show the default drawing controls
|
| 247 |
+
polygonOptions: {
|
| 248 |
+
fillColor: '#4CAF50',
|
| 249 |
+
fillOpacity: 0.3,
|
| 250 |
+
strokeWeight: 2,
|
| 251 |
+
strokeColor: '#4CAF50',
|
| 252 |
+
editable: true // Allow editing after drawing
|
| 253 |
+
}
|
| 254 |
+
});
|
| 255 |
+
drawingManager.setMap(map);
|
| 256 |
+
|
| 257 |
+
// Event listener for when a polygon is completed
|
| 258 |
+
google.maps.event.addListener(drawingManager, 'polygoncomplete', function(poly) {
|
| 259 |
+
// If there was an old polygon, clear it
|
| 260 |
+
if (polygon) {
|
| 261 |
+
polygon.setMap(null);
|
| 262 |
+
}
|
| 263 |
+
polygon = poly;
|
| 264 |
+
drawingManager.setDrawingMode(null); // Exit drawing mode
|
| 265 |
+
document.getElementById('startDrawingBtn').textContent = "Start Drawing";
|
| 266 |
+
|
| 267 |
+
if (document.getElementById('snapToGridCheck').checked) {
|
| 268 |
+
snapPolygonToGrid(polygon);
|
| 269 |
+
}
|
| 270 |
+
|
| 271 |
+
updateGeofenceData();
|
| 272 |
+
|
| 273 |
+
// Add listener for polygon path changes (for editing)
|
| 274 |
+
google.maps.event.addListener(polygon.getPath(), 'set_at', updateGeofenceData);
|
| 275 |
+
google.maps.event.addListener(polygon.getPath(), 'insert_at', updateGeofenceData);
|
| 276 |
+
});
|
| 277 |
+
}
|
| 278 |
+
|
| 279 |
+
function snapPolygonToGrid(poly) {
|
| 280 |
+
const gridSize = 0.0005; // Finer grid for more precision
|
| 281 |
+
let path = poly.getPath();
|
| 282 |
+
for (let i = 0; i < path.getLength(); i++) {
|
| 283 |
+
let pt = path.getAt(i);
|
| 284 |
+
let snappedLat = Math.round(pt.lat() / gridSize) * gridSize;
|
| 285 |
+
let snappedLng = Math.round(pt.lng() / gridSize) * gridSize;
|
| 286 |
+
path.setAt(i, new google.maps.LatLng(snappedLat, snappedLng));
|
| 287 |
+
}
|
| 288 |
+
}
|
| 289 |
+
|
| 290 |
+
function setupEventListeners() {
|
| 291 |
+
document.getElementById('clearDrawingBtn').addEventListener('click', clearAll);
|
| 292 |
+
document.getElementById('helpBtn').addEventListener('click', function() {
|
| 293 |
+
new bootstrap.Modal(document.getElementById('helpModal')).show();
|
| 294 |
+
});
|
| 295 |
+
document.getElementById('resetBtn').addEventListener('click', function() {
|
| 296 |
+
clearAll();
|
| 297 |
+
map.setCenter({ lat: 20.5937, lng: 78.9629 }); // Reset to India
|
| 298 |
+
map.setZoom(5);
|
| 299 |
+
});
|
| 300 |
+
document.getElementById('addressSearchBtn').addEventListener('click', searchByAddress);
|
| 301 |
+
document.getElementById('coordSearchBtn').addEventListener('click', searchByCoordinates);
|
| 302 |
+
document.getElementById('snapToGridCheck').addEventListener('change', function() {
|
| 303 |
+
if (polygon && this.checked) {
|
| 304 |
+
snapPolygonToGrid(polygon);
|
| 305 |
+
updateGeofenceData(); // Recalculate area after snapping
|
| 306 |
+
}
|
| 307 |
+
});
|
| 308 |
+
document.getElementById('generateDesignBtn').addEventListener('click', generateFarmDesign);
|
| 309 |
+
}
|
| 310 |
+
|
| 311 |
+
function clearPolygon() {
|
| 312 |
+
if (polygon) {
|
| 313 |
+
polygon.setMap(null);
|
| 314 |
+
polygon = null;
|
| 315 |
+
}
|
| 316 |
+
document.getElementById('geofenceInfoContent').innerHTML = '<p>Draw a polygon on the map to see its area and coordinates.</p>';
|
| 317 |
+
document.getElementById('farmDesignContent').innerHTML = '<p>Click "Generate Farm Design" after drawing a geofence and selecting your farming type.</p>';
|
| 318 |
+
}
|
| 319 |
+
|
| 320 |
+
function clearAll() {
|
| 321 |
+
clearPolygon();
|
| 322 |
+
// Reset drawing mode if it was active
|
| 323 |
+
drawingManager.setDrawingMode(null);
|
| 324 |
+
document.getElementById('startDrawingBtn').textContent = "Start Drawing";
|
| 325 |
+
}
|
| 326 |
+
|
| 327 |
+
function searchByAddress() {
|
| 328 |
+
let address = document.getElementById('addressInput').value;
|
| 329 |
+
if (!address) {
|
| 330 |
+
showToast("Please enter an address");
|
| 331 |
+
return;
|
| 332 |
+
}
|
| 333 |
+
geocoder.geocode({ address: address }, function(results, status) {
|
| 334 |
+
if (status === google.maps.GeocoderStatus.OK) {
|
| 335 |
+
let location = results[0].geometry.location;
|
| 336 |
+
map.setCenter(location);
|
| 337 |
+
map.setZoom(12);
|
| 338 |
+
} else {
|
| 339 |
+
showToast("Geocode was not successful: " + status);
|
| 340 |
+
}
|
| 341 |
+
});
|
| 342 |
+
}
|
| 343 |
+
|
| 344 |
+
function searchByCoordinates() {
|
| 345 |
+
let lat = parseFloat(document.getElementById('latInput').value);
|
| 346 |
+
let lng = parseFloat(document.getElementById('lngInput').value);
|
| 347 |
+
if (!isNaN(lat) && !isNaN(lng) && lat >= -90 && lat <= 90 && lng >= -180 && lng <= 180) {
|
| 348 |
+
map.setCenter({ lat: lat, lng: lng });
|
| 349 |
+
map.setZoom(12); // Zoom in on the specified coordinates
|
| 350 |
+
} else {
|
| 351 |
+
showToast("Invalid coordinates. Latitude must be between -90 and 90, Longitude between -180 and 180.");
|
| 352 |
+
}
|
| 353 |
+
}
|
| 354 |
+
|
| 355 |
+
function updateGeofenceData() {
|
| 356 |
+
if (!polygon) {
|
| 357 |
+
document.getElementById('geofenceInfoContent').innerHTML = '<p>Draw a polygon on the map to see its area and coordinates.</p>';
|
| 358 |
+
return;
|
| 359 |
+
}
|
| 360 |
+
|
| 361 |
+
showLoading(true);
|
| 362 |
+
|
| 363 |
+
const path = polygon.getPath();
|
| 364 |
+
const coordinates = [];
|
| 365 |
+
path.forEach(function(latLng) {
|
| 366 |
+
coordinates.push({ lat: latLng.lat(), lng: latLng.lng() });
|
| 367 |
+
});
|
| 368 |
+
|
| 369 |
+
fetch('/calculate_area', {
|
| 370 |
+
method: 'POST',
|
| 371 |
+
headers: {
|
| 372 |
+
'Content-Type': 'application/json',
|
| 373 |
+
},
|
| 374 |
+
body: JSON.stringify({ coordinates: coordinates }),
|
| 375 |
+
})
|
| 376 |
+
.then(response => response.json())
|
| 377 |
+
.then(data => {
|
| 378 |
+
showLoading(false);
|
| 379 |
+
const container = document.getElementById('geofenceInfoContent');
|
| 380 |
+
if (data.error) {
|
| 381 |
+
container.innerHTML = `<p class="text-danger">Error: ${data.error}</p>`;
|
| 382 |
+
showToast(`Error: ${data.error}`);
|
| 383 |
+
return;
|
| 384 |
+
}
|
| 385 |
+
|
| 386 |
+
let coordsHtml = '<h6>Coordinates:</h6><ul>';
|
| 387 |
+
data.coordinates.forEach(coord => {
|
| 388 |
+
coordsHtml += `<li>Lat: ${coord.lat.toFixed(6)}, Lng: ${coord.lng.toFixed(6)}</li>`;
|
| 389 |
+
});
|
| 390 |
+
coordsHtml += '</ul>';
|
| 391 |
+
|
| 392 |
+
container.innerHTML = `
|
| 393 |
+
<h6>Area:</h6>
|
| 394 |
+
<ul>
|
| 395 |
+
<li><strong>${data.area_hectares.toFixed(2)} hectares</strong></li>
|
| 396 |
+
<li>${data.area_sq_meters.toFixed(2)} sq meters</li>
|
| 397 |
+
<li>${data.area_sq_km.toFixed(2)} sq km</li>
|
| 398 |
+
<li>${data.area_acres.toFixed(2)} acres</li>
|
| 399 |
+
</ul>
|
| 400 |
+
${coordsHtml}
|
| 401 |
+
`;
|
| 402 |
+
})
|
| 403 |
+
.catch(error => {
|
| 404 |
+
console.error('Error fetching area calculation:', error);
|
| 405 |
+
showLoading(false);
|
| 406 |
+
document.getElementById('geofenceInfoContent').innerHTML = '<p class="text-danger">Failed to calculate area. Please try again.</p>';
|
| 407 |
+
showToast("Failed to calculate area.");
|
| 408 |
+
});
|
| 409 |
+
}
|
| 410 |
+
|
| 411 |
+
function generateFarmDesign() {
|
| 412 |
+
if (!polygon) {
|
| 413 |
+
showToast("Please draw a farm area (geofence) on the map first.");
|
| 414 |
+
return;
|
| 415 |
+
}
|
| 416 |
+
|
| 417 |
+
showLoading(true);
|
| 418 |
+
|
| 419 |
+
const path = polygon.getPath();
|
| 420 |
+
const coordinates = [];
|
| 421 |
+
path.forEach(function(latLng) {
|
| 422 |
+
coordinates.push({ lat: latLng.lat(), lng: latLng.lng() });
|
| 423 |
+
});
|
| 424 |
+
|
| 425 |
+
const farmType = document.getElementById('farmTypeSelect').value;
|
| 426 |
+
const preferences = document.getElementById('preferencesInput').value;
|
| 427 |
+
|
| 428 |
+
fetch('/generate_farm_design', {
|
| 429 |
+
method: 'POST',
|
| 430 |
+
headers: {
|
| 431 |
+
'Content-Type': 'application/json',
|
| 432 |
+
},
|
| 433 |
+
body: JSON.stringify({
|
| 434 |
+
coordinates: coordinates,
|
| 435 |
+
farm_type: farmType,
|
| 436 |
+
preferences: preferences
|
| 437 |
+
}),
|
| 438 |
+
})
|
| 439 |
+
.then(response => response.json())
|
| 440 |
+
.then(data => {
|
| 441 |
+
showLoading(false);
|
| 442 |
+
const container = document.getElementById('farmDesignContent');
|
| 443 |
+
if (data.error) {
|
| 444 |
+
container.innerHTML = `<p class="text-danger">Error: ${data.error}</p>`;
|
| 445 |
+
showToast(`Error: ${data.error}`);
|
| 446 |
+
return;
|
| 447 |
+
}
|
| 448 |
+
|
| 449 |
+
let designHtml = `<h6>AI-Generated Farm Design Plan for ${farmType} Farm:</h6>
|
| 450 |
+
<div class="card p-3 mb-3">
|
| 451 |
+
${data.design_plan.split('\n').map(p => `<p>${p}</p>`).join('')}
|
| 452 |
+
</div>`;
|
| 453 |
+
|
| 454 |
+
if (data.visual_design_prompt) {
|
| 455 |
+
designHtml += `<h6 class="mt-4">Prompt for Image Generation AI:</h6>
|
| 456 |
+
<div class="card p-3 bg-light">
|
| 457 |
+
<p class="mb-0"><em>"${data.visual_design_prompt}"</em></p>
|
| 458 |
+
<small class="text-muted mt-2">
|
| 459 |
+
(Use this prompt with a dedicated text-to-image AI (e.g., DALL-E, Midjourney, Stable Diffusion) to visualize the design.
|
| 460 |
+
The current Google Gemini API provides textual advice and prompts for external image generation.)
|
| 461 |
+
</small>
|
| 462 |
+
</div>`;
|
| 463 |
+
}
|
| 464 |
+
|
| 465 |
+
if (data.map_image_url) {
|
| 466 |
+
designHtml += `<h6 class="mt-4">Farm Area Context:</h6>
|
| 467 |
+
<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;">
|
| 468 |
+
<small class="text-muted d-block mt-1">Satellite image of the geofenced area used for AI analysis.</small>`;
|
| 469 |
+
}
|
| 470 |
+
|
| 471 |
+
|
| 472 |
+
container.innerHTML = designHtml;
|
| 473 |
+
showToast("Farm design generated successfully!");
|
| 474 |
+
// Switch to the AI Farm Design tab
|
| 475 |
+
new bootstrap.Tab(document.querySelector('#dataTabs a[href="#farmDesignOutput"]')).show();
|
| 476 |
+
})
|
| 477 |
+
.catch(error => {
|
| 478 |
+
console.error('Error generating farm design:', error);
|
| 479 |
+
showLoading(false);
|
| 480 |
+
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>';
|
| 481 |
+
showToast("Failed to generate farm design.");
|
| 482 |
+
});
|
| 483 |
+
}
|
| 484 |
+
|
| 485 |
+
function showLoading(show) {
|
| 486 |
+
document.getElementById('loadingOverlay').style.display = show ? 'flex' : 'none';
|
| 487 |
+
}
|
| 488 |
+
|
| 489 |
+
function showToast(message) {
|
| 490 |
+
Toastify({
|
| 491 |
+
text: message,
|
| 492 |
+
duration: 3000,
|
| 493 |
+
gravity: "top",
|
| 494 |
+
position: "right",
|
| 495 |
+
backgroundColor: "#333",
|
| 496 |
+
stopOnFocus: true
|
| 497 |
+
}).showToast();
|
| 498 |
+
}
|
| 499 |
+
|
| 500 |
+
window.onload = initMap;
|
| 501 |
+
</script>
|
| 502 |
+
</body>
|
| 503 |
+
</html>
|
ai_studio_code.py
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from flask import Flask, render_template, request, jsonify
|
| 2 |
+
import math
|
| 3 |
+
import logging
|
| 4 |
+
import os
|
| 5 |
+
import google.generativeai as genai
|
| 6 |
+
import requests
|
| 7 |
+
import base64 # For potentially handling image data, though we're using URLs for Gemini Vision
|
| 8 |
+
|
| 9 |
+
app = Flask(__name__)
|
| 10 |
+
logging.basicConfig(level=logging.INFO)
|
| 11 |
+
|
| 12 |
+
# Configuration – ensure your API keys are valid
|
| 13 |
+
# Retrieve API keys from environment variables
|
| 14 |
+
GEMINI_API_KEY = os.getenv('GEMINI_API_KEY')
|
| 15 |
+
GOOGLE_MAPS_STATIC_API_KEY = os.getenv('GOOGLE_MAPS_STATIC_API_KEY') # For fetching map images
|
| 16 |
+
|
| 17 |
+
# Configure Gemini API
|
| 18 |
+
if GEMINI_API_KEY:
|
| 19 |
+
genai.configure(api_key=GEMINI_API_KEY)
|
| 20 |
+
else:
|
| 21 |
+
logging.error("GEMINI_API_KEY not set. Gemini API functionality will be limited or unavailable.")
|
| 22 |
+
|
| 23 |
+
def validate_coordinates(lat, lon):
|
| 24 |
+
try:
|
| 25 |
+
lat = float(lat)
|
| 26 |
+
lon = float(lon)
|
| 27 |
+
if not (-90 <= lat <= 90 and -180 <= lon <= 180):
|
| 28 |
+
return None, None
|
| 29 |
+
return lat, lon
|
| 30 |
+
except (TypeError, ValueError):
|
| 31 |
+
return None, None
|
| 32 |
+
|
| 33 |
+
@app.route('/')
|
| 34 |
+
def index():
|
| 35 |
+
return render_template('index.html')
|
| 36 |
+
|
| 37 |
+
@app.route('/calculate_area', methods=['POST'])
|
| 38 |
+
def calculate_area():
|
| 39 |
+
"""
|
| 40 |
+
Calculates the area of a polygon given its coordinates in square meters.
|
| 41 |
+
Uses the Shoelace formula for area calculation on a sphere.
|
| 42 |
+
"""
|
| 43 |
+
data = request.json
|
| 44 |
+
coordinates = data.get('coordinates')
|
| 45 |
+
|
| 46 |
+
if not coordinates or not isinstance(coordinates, list) or len(coordinates) < 3:
|
| 47 |
+
return jsonify({"error": "Invalid polygon coordinates provided. Need at least 3 points."}), 400
|
| 48 |
+
|
| 49 |
+
area_sq_meters = calculate_polygon_area_haversine(coordinates)
|
| 50 |
+
if area_sq_meters is None:
|
| 51 |
+
return jsonify({"error": "Error calculating area"}), 500
|
| 52 |
+
|
| 53 |
+
area_sq_km = area_sq_meters / 1_000_000
|
| 54 |
+
area_acres = area_sq_meters * 0.000247105
|
| 55 |
+
area_hectares = area_sq_meters / 10_000
|
| 56 |
+
|
| 57 |
+
return jsonify({
|
| 58 |
+
"area_sq_meters": area_sq_meters,
|
| 59 |
+
"area_sq_km": area_sq_km,
|
| 60 |
+
"area_acres": area_acres,
|
| 61 |
+
"area_hectares": area_hectares,
|
| 62 |
+
"coordinates": coordinates
|
| 63 |
+
})
|
| 64 |
+
|
| 65 |
+
def calculate_polygon_area_haversine(coords):
|
| 66 |
+
"""
|
| 67 |
+
Calculates the area of a spherical polygon using the Haversine formula
|
| 68 |
+
for edge lengths and then applying spherical excess.
|
| 69 |
+
This is an approximation and might not be perfectly accurate for very large polygons.
|
| 70 |
+
For small farm plots, it should be sufficiently accurate.
|
| 71 |
+
"""
|
| 72 |
+
R = 6378137 # Earth's radius in meters
|
| 73 |
+
area = 0.0
|
| 74 |
+
|
| 75 |
+
if len(coords) < 3:
|
| 76 |
+
return 0.0
|
| 77 |
+
|
| 78 |
+
for i in range(len(coords)):
|
| 79 |
+
lat1_rad = math.radians(coords[i]['lat'])
|
| 80 |
+
lon1_rad = math.radians(coords[i]['lng'])
|
| 81 |
+
lat2_rad = math.radians(coords[(i + 1) % len(coords)]['lat'])
|
| 82 |
+
lon2_rad = math.radians(coords[(i + 1) % len(coords)]['lng'])
|
| 83 |
+
|
| 84 |
+
# Using Gauss's area formula for spherical polygons (related to spherical excess)
|
| 85 |
+
area += (lon2_rad - lon1_rad) * (2 + math.sin(lat1_rad) + math.sin(lat2_rad))
|
| 86 |
+
|
| 87 |
+
area = area * R * R / 2.0
|
| 88 |
+
return abs(area)
|
| 89 |
+
|
| 90 |
+
def get_polygon_center(coords):
|
| 91 |
+
"""Calculates the approximate center of a polygon."""
|
| 92 |
+
if not coords:
|
| 93 |
+
return None
|
| 94 |
+
lat_sum = sum(p['lat'] for p in coords)
|
| 95 |
+
lon_sum = sum(p['lng'] for p in coords)
|
| 96 |
+
return {'lat': lat_sum / len(coords), 'lng': lon_sum / len(coords)}
|
| 97 |
+
|
| 98 |
+
@app.route('/generate_farm_design', methods=['POST'])
|
| 99 |
+
def generate_farm_design():
|
| 100 |
+
"""
|
| 101 |
+
Generates a farm design plan using Gemini based on geofenced area,
|
| 102 |
+
farm type, and farmer's preferences.
|
| 103 |
+
"""
|
| 104 |
+
data = request.json
|
| 105 |
+
coordinates = data.get('coordinates')
|
| 106 |
+
farm_type = data.get('farm_type')
|
| 107 |
+
preferences = data.get('preferences')
|
| 108 |
+
|
| 109 |
+
if not coordinates or not isinstance(coordinates, list) or len(coordinates) < 3:
|
| 110 |
+
return jsonify({"error": "Invalid polygon coordinates provided for design. Need at least 3 points."}), 400
|
| 111 |
+
if not farm_type:
|
| 112 |
+
return jsonify({"error": "Farm type is required."}), 400
|
| 113 |
+
if not GOOGLE_MAPS_STATIC_API_KEY:
|
| 114 |
+
return jsonify({"error": "Google Maps Static API Key not configured on the server."}), 500
|
| 115 |
+
if not GEMINI_API_KEY:
|
| 116 |
+
return jsonify({"error": "Gemini API Key not configured on the server."}), 500
|
| 117 |
+
|
| 118 |
+
try:
|
| 119 |
+
# 1. Get the center of the polygon for the static map image
|
| 120 |
+
center_point = get_polygon_center(coordinates)
|
| 121 |
+
if not center_point:
|
| 122 |
+
return jsonify({"error": "Could not determine polygon center for map image."}), 500
|
| 123 |
+
|
| 124 |
+
# Construct path for the polygon on the static map
|
| 125 |
+
path_str = "color:0x4CAF50FF|weight:2|fillcolor:0x4CAF504C"
|
| 126 |
+
for coord in coordinates:
|
| 127 |
+
path_str += f"|{coord['lat']},{coord['lng']}"
|
| 128 |
+
|
| 129 |
+
# Get Google Static Map image URL
|
| 130 |
+
# Adjust zoom level based on area if possible, for simplicity using fixed for now.
|
| 131 |
+
# Max width/height for static maps is typically 640x640.
|
| 132 |
+
map_image_url = (
|
| 133 |
+
f"https://maps.googleapis.com/maps/api/staticmap?center={center_point['lat']},{center_point['lng']}"
|
| 134 |
+
f"&zoom=15&size=640x640&maptype=satellite&markers=color:red%7C{center_point['lat']},{center_point['lng']}"
|
| 135 |
+
f"&path={path_str}"
|
| 136 |
+
f"&key={GOOGLE_MAPS_STATIC_API_KEY}"
|
| 137 |
+
)
|
| 138 |
+
logging.info(f"Generated Static Map URL: {map_image_url}")
|
| 139 |
+
|
| 140 |
+
# 2. Prepare the prompt for Gemini Pro Vision
|
| 141 |
+
model = genai.GenerativeModel('gemini-pro-vision')
|
| 142 |
+
|
| 143 |
+
# Craft a comprehensive prompt for the AI
|
| 144 |
+
user_prompt = (
|
| 145 |
+
f"I am a farmer planning to set up a '{farm_type}' farm on the enclosed land shown in the satellite image. "
|
| 146 |
+
f"My additional preferences are: '{preferences}'. "
|
| 147 |
+
f"Please analyze the terrain in the image and provide a detailed, optimal farm layout plan. "
|
| 148 |
+
f"Include recommendations for:"
|
| 149 |
+
f"\n- **Overall layout design (e.g., zones for different activities, orientation)**"
|
| 150 |
+
f"\n- **Specific elements for {farm_type} farming (e.g., for horticulture: crop rows, irrigation, greenhouses; for poultry: coop placement, runs; for dairy: barn, milking parlor, pastures).**"
|
| 151 |
+
f"\n- **Resource management (water, shade, sun exposure, potential waste management).**"
|
| 152 |
+
f"\n- **Accessibility (paths, roads).**"
|
| 153 |
+
f"\n- **Any terrain-specific considerations from the image.**"
|
| 154 |
+
f"\n\nAlso, provide a short, descriptive prompt (2-3 sentences) that could be used by a text-to-image AI to visualize this proposed layout overlaid on a similar satellite image of a farm, depicting the suggested elements (e.g., 'An aerial view of a farm, clearly demarcated with rows of crops, a poultry coop, and a small dairy barn, all integrated seamlessly into the landscape with efficient pathing.')."
|
| 155 |
+
)
|
| 156 |
+
|
| 157 |
+
# 3. Call Gemini Pro Vision
|
| 158 |
+
# Gemini Pro Vision can take image URLs directly
|
| 159 |
+
response = model.generate_content([user_prompt, {'mime_type': 'image/jpeg', 'image_url': map_image_url}])
|
| 160 |
+
|
| 161 |
+
design_plan = response.text
|
| 162 |
+
logging.info(f"Gemini response: {design_plan}")
|
| 163 |
+
|
| 164 |
+
# Extract the image generation prompt from Gemini's response if it followed the instruction
|
| 165 |
+
image_gen_prompt_match = ""
|
| 166 |
+
# A simple heuristic to find the image prompt if Gemini puts it at the end
|
| 167 |
+
if "descriptive prompt" in design_plan.lower():
|
| 168 |
+
parts = design_plan.rsplit("descriptive prompt:", 1)
|
| 169 |
+
if len(parts) > 1:
|
| 170 |
+
design_plan = parts[0].strip()
|
| 171 |
+
image_gen_prompt_match = parts[1].strip()
|
| 172 |
+
|
| 173 |
+
if not image_gen_prompt_match and "text-to-image AI" in design_plan:
|
| 174 |
+
# Fallback: if Gemini embeds it differently
|
| 175 |
+
start_index = design_plan.lower().find("text-to-image ai")
|
| 176 |
+
if start_index != -1:
|
| 177 |
+
end_index = design_plan.find(".", start_index + len("text-to-image ai"))
|
| 178 |
+
if end_index != -1:
|
| 179 |
+
image_gen_prompt_match = design_plan[start_index:end_index+1]
|
| 180 |
+
# Try to remove it from the main design_plan if it's found there
|
| 181 |
+
design_plan = design_plan.replace(image_gen_prompt_match, "").strip()
|
| 182 |
+
|
| 183 |
+
|
| 184 |
+
return jsonify({
|
| 185 |
+
"design_plan": design_plan,
|
| 186 |
+
"visual_design_prompt": image_gen_prompt_match, # This is the prompt for another image gen AI
|
| 187 |
+
"map_image_url": map_image_url # Optionally return the static map URL for context
|
| 188 |
+
})
|
| 189 |
+
|
| 190 |
+
except Exception as e:
|
| 191 |
+
logging.error(f"Error generating farm design: {str(e)}")
|
| 192 |
+
# More detailed error handling for API specific issues could be added
|
| 193 |
+
if "400 Bad Request" in str(e) and "API key" in str(e):
|
| 194 |
+
return jsonify({"error": "Gemini API Key might be invalid or improperly configured.", "details": str(e)}), 500
|
| 195 |
+
return jsonify({"error": "Failed to generate farm design plan", "details": str(e)}), 503
|
| 196 |
+
|
| 197 |
+
@app.errorhandler(404)
|
| 198 |
+
def not_found_error(error):
|
| 199 |
+
return jsonify({"error": "Resource not found"}), 404
|
| 200 |
+
|
| 201 |
+
@app.errorhandler(500)
|
| 202 |
+
def internal_error(error):
|
| 203 |
+
return jsonify({"error": "Internal server error"}), 500
|
| 204 |
+
|
| 205 |
+
if __name__ == '__main__':
|
| 206 |
+
app.run(debug=True, port=5000)
|