CoolFace
Apppublic

rajkhanke/Geo_disaster_safe_construct

sourceHugging Faceupdated 1y agoView on Hugging Face
0likes
app.py307 linesDownload Raw Back to root
1from flask import Flask, request, jsonify, render_template2import json3import requests4import random5import math6import os7from dotenv import load_dotenv8 9load_dotenv()  # Load environment variables from .env file10 11app = Flask(__name__)12 13GEMINI_API_KEY = os.getenv('GEMINI_API_KEY')14NOMINATIM_USER_AGENT = "GeoSafeConstruct/1.0 (your.email@example.com)"  # IMPORTANT: Change to your app name and contact email15 16 17# --- Nominatim API Functions ---18def get_location_name_from_coords(lat, lon):19    """Fetches a location name/address from coordinates using Nominatim."""20    headers = {"User-Agent": NOMINATIM_USER_AGENT}21    url = f"https://nominatim.openstreetmap.org/reverse?format=json&lat={lat}&lon={lon}&zoom=16&addressdetails=1"22    try:23        response = requests.get(url, headers=headers, timeout=10)24        response.raise_for_status()25        data = response.json()26        # Construct a readable address, prioritizing certain fields27        address = data.get('address', {})28        parts = [29            address.get('road'), address.get('neighbourhood'), address.get('suburb'),30            address.get('city_district'), address.get('city'), address.get('town'),31            address.get('village'), address.get('county'), address.get('state'),32            address.get('postcode'), address.get('country')33        ]34        # Filter out None values and join35        display_name = data.get('display_name', "Unknown Location")36        filtered_parts = [part for part in parts if part]37        if len(filtered_parts) > 3:  # If many parts, use display_name for brevity38            return display_name39        elif filtered_parts:40            return ", ".join(filtered_parts[:3])  # Take first few relevant parts41        return display_name  # Fallback to full display_name42    except requests.exceptions.RequestException as e:43        print(f"Nominatim reverse geocoding error: {e}")44        return f"Area around {lat:.3f}, {lon:.3f}"45    except (json.JSONDecodeError, KeyError) as e:46        print(f"Nominatim response parsing error: {e}")47        return f"Area around {lat:.3f}, {lon:.3f}"48 49 50def get_coords_from_location_name(query):51    """Fetches coordinates from a location name/address using Nominatim."""52    headers = {"User-Agent": NOMINATIM_USER_AGENT}53    url = f"https://nominatim.openstreetmap.org/search?q={requests.utils.quote(query)}&format=json&limit=1"54    try:55        response = requests.get(url, headers=headers, timeout=10)56        response.raise_for_status()57        data = response.json()58        if data and isinstance(data, list) and len(data) > 0:59            return {60                "latitude": float(data[0].get("lat")),61                "longitude": float(data[0].get("lon")),62                "display_name": data[0].get("display_name")63            }64        return None65    except requests.exceptions.RequestException as e:66        print(f"Nominatim geocoding error: {e}")67        return None68    except (json.JSONDecodeError, KeyError, IndexError) as e:69        print(f"Nominatim geocoding response parsing error: {e}")70        return None71 72 73# --- Gemini API Functions ---74def call_gemini_api(prompt_text):75    """Generic function to call Gemini API."""76    if not GEMINI_API_KEY:77        print("GEMINI_API_KEY not found in environment variables.")78        return None, "Gemini API key not configured."79 80    url = "https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent"81    headers = {82        "Content-Type": "application/json",83        "x-goog-api-key": GEMINI_API_KEY84    }85    payload = {86        "contents": [{"parts": [{"text": prompt_text}]}],87        "generationConfig": {88            "response_mime_type": "application/json",89            "temperature": 0.6,  # Adjust for creativity vs. factuality90            "topP": 0.9,91            "topK": 4092        },93        "safetySettings": [  # Add safety settings if needed94            {"category": "HARM_CATEGORY_HARASSMENT", "threshold": "BLOCK_MEDIUM_AND_ABOVE"},95            {"category": "HARM_CATEGORY_HATE_SPEECH", "threshold": "BLOCK_MEDIUM_AND_ABOVE"},96            {"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT", "threshold": "BLOCK_MEDIUM_AND_ABOVE"},97            {"category": "HARM_CATEGORY_DANGEROUS_CONTENT", "threshold": "BLOCK_MEDIUM_AND_ABOVE"}98        ]99    }100 101    try:102        response = requests.post(url, json=payload, headers=headers, timeout=60)  # Increased timeout103        response.raise_for_status()104        gemini_response_data = response.json()105 106        if 'candidates' not in gemini_response_data or not gemini_response_data['candidates']:107            print("Gemini API Error: No candidates in response.")108            print("Full Gemini Response:", gemini_response_data)109            if 'promptFeedback' in gemini_response_data and 'blockReason' in gemini_response_data['promptFeedback']:110                return None, f"Content blocked by API: {gemini_response_data['promptFeedback']['blockReason']}"111            return None, "Gemini API returned no content."112 113        json_text_content = gemini_response_data['candidates'][0]['content']['parts'][0]['text']114 115        # Attempt to clean JSON string if it's wrapped in markdown116        if json_text_content.strip().startswith("```json"):117            json_text_content = json_text_content.strip()[7:]118        if json_text_content.strip().endswith("```"):119            json_text_content = json_text_content.strip()[:-3]120 121        return json.loads(json_text_content.strip()), None122    except requests.exceptions.Timeout:123        print("Gemini API call timed out.")124        return None, "Analysis request timed out. Please try again."125    except requests.exceptions.RequestException as e:126        print(f"Error calling Gemini API (RequestException): {e}")127        return None, f"Could not connect to analysis service: {e}"128    except json.JSONDecodeError as e:129        print(f"Error decoding JSON from Gemini API: {e}")130        print(f"Received text for JSON parsing: {json_text_content if 'json_text_content' in locals() else 'N/A'}")131        return None, "AI returned an invalid response format. Please try again."132    except (KeyError, IndexError) as e:133        print(f"Error parsing Gemini API response structure: {e}")134        print(f"Gemini response: {gemini_response_data if 'gemini_response_data' in locals() else 'N/A'}")135        return None, "AI returned an unexpected response structure."136    except Exception as e:137        print(f"An unexpected error occurred during Gemini call: {e}")138        return None, f"An unexpected error occurred: {e}"139 140 141def get_safety_analysis_with_gemini(latitude, longitude, location_address):142    """143    Calls Gemini API to analyze construction safety.144    """145    prompt = f"""146    **Objective:** Provide a detailed and realistic construction site safety analysis for the location: {location_address} (Latitude: {latitude}, Longitude: {longitude}).147 148    **Output Format:** STRICTLY JSON. Do NOT include any text outside the JSON structure (e.g., no '```json' or '```' wrappers).149 150    **JSON Structure:**151    {{152        "location_name": "{location_address}", // Use the provided address153        "safety_score": "integer (0-100, overall safety score, be critical and realistic)",154        "suitability_statement": "string (e.g., 'Generally Suitable with Mitigations', 'High Caution Advised', 'Not Recommended without Major Intervention')",155        "summary_assessment": "string (3-4 sentences summarizing key findings, suitability, and major concerns. Be specific.)",156        "geological_risks": {{157            "earthquake_risk": {{158                "level": "string ('Low', 'Medium', 'High', 'Very High', 'Not Assessed')",159                "details": "string (Specifics like proximity to faults, historical activity, soil liquefaction potential if known. If not assessed, state why.)"160            }},161            "landslide_risk": {{162                "level": "string ('Low', 'Medium', 'High', 'Very High', 'Not Assessed')",163                "details": "string (Slope stability, soil type, vegetation, historical incidents. If not assessed, state why.)"164            }},165            "soil_stability": {{166                "type": "string ('Stable', 'Moderately Stable', 'Unstable', 'Variable', 'Requires Investigation')",167                "concerns": ["string array (e.g., 'Expansive clays present', 'High water table', 'Poor drainage', 'Subsidence risk')"]168            }}169        }},170        "hydrological_risks": {{171            "flood_risk": {{172                "level": "string ('Low', 'Medium', 'High', 'Very High', 'Not Assessed')",173                "details": "string (Proximity to water bodies, floodplain maps, historical flooding, drainage issues. If not assessed, state why.)"174            }},175            "tsunami_risk": {{ // Only include if geographically relevant (coastal)176                "level": "string ('Negligible', 'Low', 'Medium', 'High')",177                "details": "string (Distance from coast, elevation, historical data.)"178            }}179        }},180        "other_environmental_risks": [ // Array of objects, include if relevant181            {{ "type": "Wildfire", "level": "string ('Low', 'Medium', 'High')", "details": "string (Vegetation, climate, fire history)" }},182            {{ "type": "Extreme Weather (Hurricanes/Tornadoes/High Winds)", "level": "string ('Low', 'Medium', 'High')", "details": "string (Regional patterns, building code requirements)" }},183            {{ "type": "Industrial Hazards", "level": "string ('Low', 'Medium', 'High')", "details": "string (Proximity to industrial plants, pipelines, hazardous material routes)" }}184        ],185        "key_risk_factors_summary": ["string array (Bulleted list of 3-5 most critical risk factors for this specific site)"],186        "mitigation_recommendations": ["string array (Actionable, specific recommendations, e.g., 'Conduct Level 2 Geotechnical Survey focusing on shear strength', 'Implement earthquake-resistant design to Zone IV standards', 'Elevate foundation by 1.5m above base flood elevation')"],187        "further_investigations_needed": ["string array (e.g., 'Detailed hydrological study', 'Environmental Impact Assessment', 'Traffic impact study')"],188        "alternative_locations_analysis": [ // Up to 2-3 alternatives. If none are significantly better, state that.189            {{190                "name_suggestion": "string (Suggest a conceptual name like 'North Ridge Site' or 'Valley View Plot')",191                "latitude": "float (as string, e.g., '18.5234')",192                "longitude": "float (as string, e.g., '73.8567')",193                "estimated_distance_km": "float (as string, e.g., '8.5')",194                "brief_justification": "string (Why is this a potential alternative? E.g., 'Appears to be on more stable ground', 'Further from flood plain')",195                "potential_pros": ["string array"],196                "potential_cons": ["string array"],197                "comparative_safety_score_estimate": "integer (0-100, relative to primary site)"198            }}199        ],200        "data_confidence_level": "string ('Low', 'Medium', 'High' - based on assumed availability of public data for this general region, not specific site data which is unknown to you)",201        "disclaimer": "This AI-generated analysis is for preliminary informational purposes only and not a substitute for professional engineering, geological, and environmental assessments. On-site investigations are crucial."202    }}203 204    **Guidelines for Content:**205    - make sumre rnaodmness in score generation not same view ppijn t everytime206    - Be realistic. If data for a specific risk is unlikely to be publicly available for a random coordinate, mark it 'Not Assessed' and explain.207    - Focus on actionable insights.208    - For alternative locations, provide *different* lat/lon coordinates that are plausibly nearby (within 5-20km).209    - Ensure all string values are properly quoted. Ensure latitudes and longitudes for alternatives are strings representing floats.210    - The safety_score should reflect a comprehensive evaluation of all risks.211    - proper spacing and upo down margins shoudl be there212    """213    return call_gemini_api(prompt)214 215 216# --- Flask Routes ---217@app.route('/')218def index():219    return render_template('index.html')220 221 222@app.route('/api/geocode', methods=['POST'])223def geocode_location():224    data = request.json225    query = data.get('query')226    if not query:227        return jsonify({"error": "Query parameter is required."}), 400228 229    coords_data = get_coords_from_location_name(query)230    if coords_data:231        return jsonify(coords_data), 200232    else:233        return jsonify({"error": "Location not found or geocoding failed."}), 404234 235 236@app.route('/api/analyze_location', methods=['POST'])237def analyze_location_route():238    data = request.json239    try:240        latitude = float(data.get('latitude'))241        longitude = float(data.get('longitude'))242    except (TypeError, ValueError):243        return jsonify({"error": "Invalid latitude or longitude provided."}), 400244 245    # Get a human-readable name for the primary location246    primary_location_address = get_location_name_from_coords(latitude, longitude)247 248    # Call Gemini for the main analysis249    analysis_data, error = get_safety_analysis_with_gemini(latitude, longitude, primary_location_address)250 251    if error:252        # Fallback to a simpler mock-like structure if Gemini fails critically253        return jsonify({254            "error_message": error,255            "location_name": primary_location_address,256            "safety_score": random.randint(20, 50),  # Low score to indicate issue257            "summary_assessment": f"Could not perform detailed analysis due to: {error}. Basic location: {primary_location_address}.",258            "suitability_statement": "Analysis Incomplete",259            "geological_risks": {"earthquake_risk": {"level": "Not Assessed", "details": error}},260            "hydrological_risks": {"flood_risk": {"level": "Not Assessed", "details": error}},261            "disclaimer": "A technical issue prevented full analysis."262        }), 500263 264    # Post-process alternative locations to get their names265    if analysis_data.get("alternative_locations_analysis"):266        for alt_loc in analysis_data["alternative_locations_analysis"]:267            try:268                alt_lat = float(alt_loc.get("latitude"))269                alt_lon = float(alt_loc.get("longitude"))270                alt_loc["actual_name"] = get_location_name_from_coords(alt_lat, alt_lon)271                # Ensure distance is calculated if not provided by Gemini or if it's nonsensical272                if not alt_loc.get("estimated_distance_km") or float(alt_loc.get("estimated_distance_km", 0)) == 0:273                    alt_loc["estimated_distance_km"] = str(274                        calculate_haversine_distance(latitude, longitude, alt_lat, alt_lon))275 276            except (TypeError, ValueError, KeyError) as e:277                print(f"Error processing alternative location coords: {e}, data: {alt_loc}")278                alt_loc["actual_name"] = "Nearby Area (details unavailable)"279                # Fallback if lat/lon are missing or invalid for an alternative280                if "latitude" not in alt_loc or "longitude" not in alt_loc:281                    alt_loc["latitude"] = str(latitude + (random.random() - 0.5) * 0.05)  # Small offset282                    alt_loc["longitude"] = str(longitude + (random.random() - 0.5) * 0.05)283 284    return jsonify(analysis_data), 200285 286 287def calculate_haversine_distance(lat1, lon1, lat2, lon2):288    R = 6371  # Radius of Earth in kilometers289    try:290        lat1_rad, lon1_rad = math.radians(float(lat1)), math.radians(float(lon1))291        lat2_rad, lon2_rad = math.radians(float(lat2)), math.radians(float(lon2))292    except (ValueError, TypeError):293        print(f"Error converting lat/lon to float for Haversine: {lat1}, {lon1}, {lat2}, {lon2}")294        return 0.0  # Fallback295    dlon = lon2_rad - lon1_rad296    dlat = lat2_rad - lat1_rad297    a = math.sin(dlat / 2) ** 2 + math.cos(lat1_rad) * math.cos(lat2_rad) * math.sin(dlon / 2) ** 2298    c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a))299    distance = R * c300    return round(distance, 1)301 302 303if __name__ == '__main__':304    if not GEMINI_API_KEY:305        print("CRITICAL ERROR: GEMINI_API_KEY is not set in the environment.")306        print("Please create a .env file with GEMINI_API_KEY=YOUR_API_KEY or set it as an environment variable.")307    app.run(debug=True, port=5000)