File size: 9,264 Bytes
42c7a8a
 
 
 
 
 
 
 
 
 
 
 
 
12fb30c
42c7a8a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4996fd0
42c7a8a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
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)