rajkhanke commited on
Commit
05342f4
·
verified ·
1 Parent(s): bcc651e

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +347 -0
app.py ADDED
@@ -0,0 +1,347 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Flask, render_template, request, jsonify
2
+ import requests
3
+ import math
4
+ import random
5
+ from datetime import datetime, timedelta
6
+ import logging
7
+ from functools import wraps
8
+ import os
9
+
10
+ app = Flask(__name__)
11
+ logging.basicConfig(level=logging.INFO)
12
+
13
+ # Configuration – ensure your API keys are valid
14
+ NEWS_API_KEY = os.getenv('NEWS_API_KEY')
15
+ DISASTER_API_KEY = os.getenv('DISASTER_API_KEY')
16
+ WEATHER_RETRY_ATTEMPTS = 3
17
+
18
+ def retry_on_failure(max_attempts=3):
19
+ def decorator(func):
20
+ @wraps(func)
21
+ def wrapper(*args, **kwargs):
22
+ for attempt in range(max_attempts):
23
+ try:
24
+ return func(*args, **kwargs)
25
+ except Exception as e:
26
+ if attempt == max_attempts - 1:
27
+ logging.error(f"Failed after {max_attempts} attempts: {str(e)}")
28
+ raise
29
+ logging.warning(f"Attempt {attempt + 1} failed: {str(e)}")
30
+ return None
31
+ return wrapper
32
+ return decorator
33
+
34
+ def validate_coordinates(lat, lon):
35
+ try:
36
+ lat = float(lat)
37
+ lon = float(lon)
38
+ if not (-90 <= lat <= 90 and -180 <= lon <= 180):
39
+ return None, None
40
+ return lat, lon
41
+ except (TypeError, ValueError):
42
+ return None, None
43
+
44
+ def get_destination_point(lat, lon, bearing, distance):
45
+ """Calculate destination point given distance (in km) and bearing."""
46
+ R = 6371.0 # Earth's radius in km
47
+ try:
48
+ bearing_rad = math.radians(bearing)
49
+ lat_rad = math.radians(lat)
50
+ lon_rad = math.radians(lon)
51
+ angular_distance = distance / R
52
+ new_lat_rad = math.asin(
53
+ math.sin(lat_rad) * math.cos(angular_distance) +
54
+ math.cos(lat_rad) * math.sin(angular_distance) * math.cos(bearing_rad)
55
+ )
56
+ new_lon_rad = lon_rad + math.atan2(
57
+ math.sin(bearing_rad) * math.sin(angular_distance) * math.cos(lat_rad),
58
+ math.cos(angular_distance) - math.sin(lat_rad) * math.sin(new_lat_rad)
59
+ )
60
+ new_lon_rad = (new_lon_rad + 3 * math.pi) % (2 * math.pi) - math.pi
61
+ return math.degrees(new_lat_rad), math.degrees(new_lon_rad)
62
+ except Exception as e:
63
+ logging.error(f"Error in get_destination_point: {str(e)}")
64
+ return None, None
65
+
66
+ @app.route('/get_full_weather', methods=['GET'])
67
+ @retry_on_failure(WEATHER_RETRY_ATTEMPTS)
68
+ def get_full_weather():
69
+ """Get detailed weather for a specific location."""
70
+ lat = request.args.get('lat')
71
+ lon = request.args.get('lon')
72
+ lat, lon = validate_coordinates(lat, lon)
73
+ if lat is None or lon is None:
74
+ return jsonify({"error": "Invalid coordinates provided", "details": "Latitude must be between -90 and 90, longitude between -180 and 180"}), 400
75
+ try:
76
+ params = {
77
+ "latitude": lat,
78
+ "longitude": lon,
79
+ "hourly": "temperature_2m,relativehumidity_2m,cloudcover,precipitation,uv_index,soil_moisture_0_1cm,windspeed_10m,winddirection_10m,weathercode",
80
+ "daily": "sunrise,sunset,precipitation_sum,temperature_2m_max,temperature_2m_min",
81
+ "current_weather": "true",
82
+ "timezone": "auto"
83
+ }
84
+ response = requests.get("https://api.open-meteo.com/v1/forecast", params=params)
85
+ response.raise_for_status()
86
+ data = response.json()
87
+ current_time_str = datetime.now().strftime("%Y-%m-%dT%H:00")
88
+ if 'hourly' in data and 'time' in data['hourly']:
89
+ try:
90
+ idx = data['hourly']['time'].index(current_time_str)
91
+ except ValueError:
92
+ idx = 0
93
+ data['current_conditions'] = {
94
+ 'temperature': data['hourly']['temperature_2m'][idx],
95
+ 'humidity': data['hourly']['relativehumidity_2m'][idx] if "relativehumidity_2m" in data['hourly'] else "No data",
96
+ 'cloudcover': data['hourly']['cloudcover'][idx] if "cloudcover" in data['hourly'] else "No data",
97
+ 'uv_index': data['hourly']['uv_index'][idx] if "uv_index" in data['hourly'] else "No data",
98
+ 'soil_moisture': data['hourly'].get('soil_moisture_0_1cm', [None])[idx] or "No data",
99
+ 'windspeed': data['hourly']['windspeed_10m'][idx],
100
+ 'winddirection': data['hourly']['winddirection_10m'][idx],
101
+ 'weathercode': data['hourly']['weathercode'][idx] if "weathercode" in data['hourly'] else None
102
+ }
103
+ else:
104
+ data['current_conditions'] = data.get('current_weather', {})
105
+ return jsonify(data)
106
+ except requests.exceptions.RequestException as e:
107
+ logging.error(f"Weather API error: {str(e)}")
108
+ return jsonify({"error": "Failed to fetch weather data", "details": str(e)}), 503
109
+
110
+ @app.route('/get_circle_weather', methods=['GET'])
111
+ def get_circle_weather():
112
+ """Get weather data for 10 random points around the fixed farm center."""
113
+ lat = request.args.get('lat')
114
+ lon = request.args.get('lon')
115
+ radius = request.args.get('radius', default=200, type=float)
116
+ lat, lon = validate_coordinates(lat, lon)
117
+ if lat is None or lon is None:
118
+ return jsonify({"error": "Invalid coordinates provided"}), 400
119
+ points_weather = []
120
+ num_points = 10
121
+ for _ in range(num_points):
122
+ random_distance = random.uniform(0, radius)
123
+ random_angle = random.uniform(0, 360)
124
+ dest_lat, dest_lon = get_destination_point(lat, lon, random_angle, random_distance)
125
+ if dest_lat is None or dest_lon is None:
126
+ continue
127
+ try:
128
+ params = {
129
+ "latitude": dest_lat,
130
+ "longitude": dest_lon,
131
+ "hourly": "temperature_2m,weathercode,relativehumidity_2m,surface_pressure,soil_moisture_0_1cm,windspeed_10m,uv_index,cloudcover",
132
+ "daily": "temperature_2m_min,temperature_2m_max,sunrise,sunset",
133
+ "current_weather": "true",
134
+ "timezone": "auto"
135
+ }
136
+ response = requests.get("https://api.open-meteo.com/v1/forecast", params=params)
137
+ response.raise_for_status()
138
+ weather_data = response.json()
139
+ if "hourly" in weather_data and "time" in weather_data["hourly"]:
140
+ try:
141
+ idx = weather_data["hourly"]["time"].index(datetime.now().strftime("%Y-%m-%dT%H:00"))
142
+ except ValueError:
143
+ idx = 0
144
+ current_data = {
145
+ "temperature": weather_data["hourly"]["temperature_2m"][idx],
146
+ "weathercode": weather_data["hourly"]["weathercode"][idx],
147
+ "relativehumidity": weather_data["hourly"]["relativehumidity_2m"][idx] if "relativehumidity_2m" in weather_data["hourly"] else "No data",
148
+ "surface_pressure": weather_data["hourly"].get("surface_pressure", [None])[idx] or "No data",
149
+ "soil_moisture": weather_data["hourly"].get("soil_moisture_0_1cm", [None])[idx] or "No data",
150
+ "windspeed": weather_data["hourly"]["windspeed_10m"][idx],
151
+ "uv_index": weather_data["hourly"]["uv_index"][idx] if "uv_index" in weather_data["hourly"] else "No data",
152
+ "cloudcover": weather_data["hourly"]["cloudcover"][idx] if "cloudcover" in weather_data["hourly"] else "No data"
153
+ }
154
+ else:
155
+ current_data = weather_data.get("current_weather", {})
156
+ processed_data = {
157
+ "location": {
158
+ "lat": dest_lat,
159
+ "lon": dest_lon,
160
+ "random_angle": random_angle,
161
+ "random_distance": random_distance
162
+ },
163
+ "current_weather": current_data,
164
+ "daily": weather_data.get("daily", {})
165
+ }
166
+ points_weather.append(processed_data)
167
+ except Exception as e:
168
+ logging.error(f"Error fetching circle weather: {str(e)}")
169
+ continue
170
+ if not points_weather:
171
+ return jsonify({"error": "Failed to fetch weather data for points"}), 503
172
+ return jsonify(points_weather)
173
+
174
+ @app.route('/get_india_disaster_events', methods=['GET'])
175
+ def get_india_disaster_events():
176
+ """
177
+ Fetch disaster events in India using the Ambee API.
178
+ Optionally filter by event type using the "disaster_type" query parameter.
179
+ """
180
+ try:
181
+ disaster_filter = request.args.get('disaster_type', '').strip().lower()
182
+ url = "https://api.ambeedata.com/disasters/latest/by-country-code"
183
+ params = {
184
+ "countryCode": "IND",
185
+ "limit": 50,
186
+ "page": 1
187
+ }
188
+ headers = {
189
+ "x-api-key": DISASTER_API_KEY
190
+ }
191
+ response = requests.get(url, params=params, headers=headers)
192
+ response.raise_for_status()
193
+ events_data = response.json() # Contains keys like "message", "hasNextPage", "result", etc.
194
+ if disaster_filter and "result" in events_data:
195
+ filtered = [event for event in events_data["result"]
196
+ if disaster_filter in (event.get("event_type", "").lower())]
197
+ events_data["result"] = filtered
198
+ return jsonify(events_data)
199
+ except Exception as e:
200
+ logging.error(f"Error fetching disaster events: {str(e)}")
201
+ return jsonify({"error": "Failed to fetch disaster events", "details": str(e)}), 503
202
+
203
+
204
+ @app.route('/get_soil_classification', methods=['GET'])
205
+ def get_soil_classification():
206
+ """Fetch soil classification data using SoilGrids API."""
207
+ lat = request.args.get('lat')
208
+ lon = request.args.get('lon')
209
+ lat, lon = validate_coordinates(lat, lon)
210
+ if lat is None or lon is None:
211
+ return jsonify({"error": "Invalid coordinates"}), 400
212
+ try:
213
+ class_url = "https://dev-rest.isric.org/soilgrids/v2.0/classification/query"
214
+ class_params = {
215
+ "lon": str(lon),
216
+ "lat": str(lat),
217
+ "number_classes": "5"
218
+ }
219
+ headers = {"accept": "application/json"}
220
+ response = requests.get(class_url, params=class_params, headers=headers)
221
+ response.raise_for_status()
222
+ class_data = response.json()
223
+ soil_type = class_data.get("wrb_class_name", "Unknown")
224
+ soil_probabilities = class_data.get("wrb_class_probability", [])
225
+ soil_category = classify_soil_category(soil_type)
226
+ return jsonify({
227
+ "soil_type": soil_type,
228
+ "soil_probabilities": soil_probabilities,
229
+ "soil_category": soil_category
230
+ })
231
+ except Exception as e:
232
+ logging.error(f"Soil classification error: {str(e)}")
233
+ return jsonify({"error": "Failed to fetch soil classification", "details": str(e)}), 503
234
+
235
+ @app.route('/get_soil_properties', methods=['GET'])
236
+ def get_soil_properties():
237
+ """Fetch soil properties data using SoilGrids API and map parameters to user-friendly names."""
238
+ lat = request.args.get('lat')
239
+ lon = request.args.get('lon')
240
+ lat, lon = validate_coordinates(lat, lon)
241
+ if lat is None or lon is None:
242
+ return jsonify({"error": "Invalid coordinates"}), 400
243
+ try:
244
+ prop_url = "https://dev-rest.isric.org/soilgrids/v2.0/properties/query"
245
+ prop_params = {
246
+ "lon": str(lon),
247
+ "lat": str(lat),
248
+ "property": [
249
+ "bdod", "cec", "cfvo", "clay", "nitrogen",
250
+ "ocd", "phh2o", "sand", "silt",
251
+ "soc", "wv0010", "wv0033", "wv1500"
252
+ ],
253
+ "depth": "5-15cm",
254
+ "value": "mean"
255
+ }
256
+ headers = {"accept": "application/json"}
257
+ response = requests.get(prop_url, params=prop_params, headers=headers)
258
+ response.raise_for_status()
259
+ prop_data = response.json()
260
+ table_data = []
261
+ PARAMETER_NAMES = {
262
+ "bdod": "Bulk Density",
263
+ "cec": "CEC",
264
+ "cfvo": "Field Capacity",
265
+ "clay": "Clay",
266
+ "nitrogen": "Nitrogen",
267
+ "ocd": "Organic Carbon Density",
268
+ "phh2o": "pH",
269
+ "sand": "Sand",
270
+ "silt": "Silt",
271
+ "soc": "Soil Organic Carbon",
272
+ "wv0010": "Volumetric Water Content (0-10cm)",
273
+ "wv0033": "Volumetric Water Content (10-33cm)",
274
+ "wv1500": "Volumetric Water Content (1500)"
275
+ }
276
+ for layer in prop_data['properties']['layers']:
277
+ parameter = layer['name']
278
+ display_name = PARAMETER_NAMES.get(parameter, parameter)
279
+ value = layer['depths'][0]['values']['mean']
280
+ if parameter in ["wv0010", "wv0033", "wv1500"]:
281
+ final_value = value / 10.0
282
+ unit = layer['unit_measure'].get("target_units", "")
283
+ elif parameter in ["phh2o"]:
284
+ final_value = value / 10.0
285
+ unit = layer['unit_measure'].get("mapped_units", "").replace("*10", "").strip()
286
+ else:
287
+ final_value = value
288
+ unit = layer['unit_measure'].get("mapped_units", "")
289
+ table_data.append([display_name, final_value, unit])
290
+ return jsonify({
291
+ "soil_properties": table_data
292
+ })
293
+ except Exception as e:
294
+ logging.error(f"Soil properties error: {str(e)}")
295
+ return jsonify({"error": "Failed to fetch soil properties", "details": str(e)}), 503
296
+
297
+ def classify_disaster_type(title, description):
298
+ text = (title + " " + (description or "")).lower()
299
+ disaster_keywords = {
300
+ 'tornado': ['tornado'],
301
+ 'flood': ['flood', 'flooding'],
302
+ 'earthquake': ['earthquake', 'seismic'],
303
+ 'wildfire': ['wildfire', 'fire'],
304
+ 'cyclone': ['cyclone', 'hurricane', 'typhoon', 'storm']
305
+ }
306
+ for dtype, keywords in disaster_keywords.items():
307
+ if any(keyword in text for keyword in keywords):
308
+ return dtype
309
+ return 'other'
310
+
311
+ def estimate_severity(title, description):
312
+ text = (title + " " + (description or "")).lower()
313
+ severity_keywords = {
314
+ 'high': ['catastrophic', 'devastating', 'emergency', 'evacuate', 'death', 'fatal'],
315
+ 'medium': ['severe', 'significant', 'major', 'warning', 'damage'],
316
+ 'low': ['minor', 'small', 'limited', 'advisory', 'watch']
317
+ }
318
+ for severity, keywords in severity_keywords.items():
319
+ if any(keyword in text for keyword in keywords):
320
+ return severity
321
+ return 'unknown'
322
+
323
+ def classify_soil_category(soil_type):
324
+ mapping = {
325
+ "vertisols": "black",
326
+ "cambisols": "red",
327
+ "luvisols": "black",
328
+ "fluvisols": "alluvial",
329
+ "gleysols": "alluvial"
330
+ }
331
+ key = soil_type.lower().strip()
332
+ return mapping.get(key, "unknown")
333
+
334
+ @app.route('/')
335
+ def index():
336
+ return render_template('index.html')
337
+
338
+ @app.errorhandler(404)
339
+ def not_found_error(error):
340
+ return jsonify({"error": "Resource not found"}), 404
341
+
342
+ @app.errorhandler(500)
343
+ def internal_error(error):
344
+ return jsonify({"error": "Internal server error"}), 500
345
+
346
+ if __name__ == '__main__':
347
+ app.run(debug=True)