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)