Farmplanner / app.py
pranit144's picture
Update app.py
12fb30c verified
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
@app.route('/')
def index():
return render_template('index.html')
@app.route('/calculate_area', methods=['POST'])
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)}
@app.route('/generate_farm_design', methods=['POST'])
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
@app.errorhandler(404)
def not_found_error(error):
return jsonify({"error": "Resource not found"}), 404
@app.errorhandler(500)
def internal_error(error):
return jsonify({"error": "Internal server error"}), 500
if __name__ == '__main__':
app.run(debug=True, port=5000)