Spaces:
Running
Running
from flask import Flask, render_template, request, jsonify | |
import math | |
import logging | |
import os | |
import google.generativeai as genai | |
import requests | |
import base64 # For potentially handling image data, though we're using URLs for Gemini Vision | |
app = Flask(__name__) | |
logging.basicConfig(level=logging.INFO) | |
# Configuration – ensure your API keys are valid | |
# Retrieve API keys from environment variables | |
GEMINI_API_KEY ="AIzaSyCe4TCtUC8C9EzYMiQP8VykIS1r4gHZKg0" | |
GOOGLE_MAPS_STATIC_API_KEY = os.getenv('GOOGLE_MAPS_STATIC_API_KEY') # For fetching map images | |
# Configure Gemini API | |
if GEMINI_API_KEY: | |
genai.configure(api_key=GEMINI_API_KEY) | |
else: | |
logging.error("GEMINI_API_KEY not set. Gemini API functionality will be limited or unavailable.") | |
def validate_coordinates(lat, lon): | |
try: | |
lat = float(lat) | |
lon = float(lon) | |
if not (-90 <= lat <= 90 and -180 <= lon <= 180): | |
return None, None | |
return lat, lon | |
except (TypeError, ValueError): | |
return None, None | |
def index(): | |
return render_template('index.html') | |
def calculate_area(): | |
""" | |
Calculates the area of a polygon given its coordinates in square meters. | |
Uses the Shoelace formula for area calculation on a sphere. | |
""" | |
data = request.json | |
coordinates = data.get('coordinates') | |
if not coordinates or not isinstance(coordinates, list) or len(coordinates) < 3: | |
return jsonify({"error": "Invalid polygon coordinates provided. Need at least 3 points."}), 400 | |
area_sq_meters = calculate_polygon_area_haversine(coordinates) | |
if area_sq_meters is None: | |
return jsonify({"error": "Error calculating area"}), 500 | |
area_sq_km = area_sq_meters / 1_000_000 | |
area_acres = area_sq_meters * 0.000247105 | |
area_hectares = area_sq_meters / 10_000 | |
return jsonify({ | |
"area_sq_meters": area_sq_meters, | |
"area_sq_km": area_sq_km, | |
"area_acres": area_acres, | |
"area_hectares": area_hectares, | |
"coordinates": coordinates | |
}) | |
def calculate_polygon_area_haversine(coords): | |
""" | |
Calculates the area of a spherical polygon using the Haversine formula | |
for edge lengths and then applying spherical excess. | |
This is an approximation and might not be perfectly accurate for very large polygons. | |
For small farm plots, it should be sufficiently accurate. | |
""" | |
R = 6378137 # Earth's radius in meters | |
area = 0.0 | |
if len(coords) < 3: | |
return 0.0 | |
for i in range(len(coords)): | |
lat1_rad = math.radians(coords[i]['lat']) | |
lon1_rad = math.radians(coords[i]['lng']) | |
lat2_rad = math.radians(coords[(i + 1) % len(coords)]['lat']) | |
lon2_rad = math.radians(coords[(i + 1) % len(coords)]['lng']) | |
# Using Gauss's area formula for spherical polygons (related to spherical excess) | |
area += (lon2_rad - lon1_rad) * (2 + math.sin(lat1_rad) + math.sin(lat2_rad)) | |
area = area * R * R / 2.0 | |
return abs(area) | |
def get_polygon_center(coords): | |
"""Calculates the approximate center of a polygon.""" | |
if not coords: | |
return None | |
lat_sum = sum(p['lat'] for p in coords) | |
lon_sum = sum(p['lng'] for p in coords) | |
return {'lat': lat_sum / len(coords), 'lng': lon_sum / len(coords)} | |
def generate_farm_design(): | |
""" | |
Generates a farm design plan using Gemini based on geofenced area, | |
farm type, and farmer's preferences. | |
""" | |
data = request.json | |
coordinates = data.get('coordinates') | |
farm_type = data.get('farm_type') | |
preferences = data.get('preferences') | |
if not coordinates or not isinstance(coordinates, list) or len(coordinates) < 3: | |
return jsonify({"error": "Invalid polygon coordinates provided for design. Need at least 3 points."}), 400 | |
if not farm_type: | |
return jsonify({"error": "Farm type is required."}), 400 | |
if not GOOGLE_MAPS_STATIC_API_KEY: | |
return jsonify({"error": "Google Maps Static API Key not configured on the server."}), 500 | |
if not GEMINI_API_KEY: | |
return jsonify({"error": "Gemini API Key not configured on the server."}), 500 | |
try: | |
# 1. Get the center of the polygon for the static map image | |
center_point = get_polygon_center(coordinates) | |
if not center_point: | |
return jsonify({"error": "Could not determine polygon center for map image."}), 500 | |
# Construct path for the polygon on the static map | |
path_str = "color:0x4CAF50FF|weight:2|fillcolor:0x4CAF504C" | |
for coord in coordinates: | |
path_str += f"|{coord['lat']},{coord['lng']}" | |
# Get Google Static Map image URL | |
# Adjust zoom level based on area if possible, for simplicity using fixed for now. | |
# Max width/height for static maps is typically 640x640. | |
map_image_url = ( | |
f"https://maps.googleapis.com/maps/api/staticmap?center={center_point['lat']},{center_point['lng']}" | |
f"&zoom=15&size=640x640&maptype=satellite&markers=color:red%7C{center_point['lat']},{center_point['lng']}" | |
f"&path={path_str}" | |
f"&key={GOOGLE_MAPS_STATIC_API_KEY}" | |
) | |
logging.info(f"Generated Static Map URL: {map_image_url}") | |
# 2. Prepare the prompt for Gemini Pro Vision | |
model = genai.GenerativeModel('gemini-2.5-flash-image-preview') | |
# Craft a comprehensive prompt for the AI | |
user_prompt = ( | |
f"I am a farmer planning to set up a '{farm_type}' farm on the enclosed land shown in the satellite image. " | |
f"My additional preferences are: '{preferences}'. " | |
f"Please analyze the terrain in the image and provide a detailed, optimal farm layout plan. " | |
f"Include recommendations for:" | |
f"\n- **Overall layout design (e.g., zones for different activities, orientation)**" | |
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).**" | |
f"\n- **Resource management (water, shade, sun exposure, potential waste management).**" | |
f"\n- **Accessibility (paths, roads).**" | |
f"\n- **Any terrain-specific considerations from the image.**" | |
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.')." | |
) | |
# 3. Call Gemini Pro Vision | |
# Gemini Pro Vision can take image URLs directly | |
response = model.generate_content([user_prompt, {'mime_type': 'image/jpeg', 'image_url': map_image_url}]) | |
design_plan = response.text | |
logging.info(f"Gemini response: {design_plan}") | |
# Extract the image generation prompt from Gemini's response if it followed the instruction | |
image_gen_prompt_match = "" | |
# A simple heuristic to find the image prompt if Gemini puts it at the end | |
if "descriptive prompt" in design_plan.lower(): | |
parts = design_plan.rsplit("descriptive prompt:", 1) | |
if len(parts) > 1: | |
design_plan = parts[0].strip() | |
image_gen_prompt_match = parts[1].strip() | |
if not image_gen_prompt_match and "text-to-image AI" in design_plan: | |
# Fallback: if Gemini embeds it differently | |
start_index = design_plan.lower().find("text-to-image ai") | |
if start_index != -1: | |
end_index = design_plan.find(".", start_index + len("text-to-image ai")) | |
if end_index != -1: | |
image_gen_prompt_match = design_plan[start_index:end_index+1] | |
# Try to remove it from the main design_plan if it's found there | |
design_plan = design_plan.replace(image_gen_prompt_match, "").strip() | |
return jsonify({ | |
"design_plan": design_plan, | |
"visual_design_prompt": image_gen_prompt_match, # This is the prompt for another image gen AI | |
"map_image_url": map_image_url # Optionally return the static map URL for context | |
}) | |
except Exception as e: | |
logging.error(f"Error generating farm design: {str(e)}") | |
# More detailed error handling for API specific issues could be added | |
if "400 Bad Request" in str(e) and "API key" in str(e): | |
return jsonify({"error": "Gemini API Key might be invalid or improperly configured.", "details": str(e)}), 500 | |
return jsonify({"error": "Failed to generate farm design plan", "details": str(e)}), 503 | |
def not_found_error(error): | |
return jsonify({"error": "Resource not found"}), 404 | |
def internal_error(error): | |
return jsonify({"error": "Internal server error"}), 500 | |
if __name__ == '__main__': | |
app.run(debug=True, port=5000) |