From 651dd8ed166c95dd8a8b6f52b42933c3f6746c24 Mon Sep 17 00:00:00 2001 From: Jonah Lefkoff Date: Sun, 17 Aug 2025 21:57:37 -0600 Subject: [PATCH 1/4] add sigmet filtering within 150nm of center --- blueprints/weather_bp.py | 47 +++++++++++++++++++++++----------------- libs/helpers.py | 11 +++++++++- 2 files changed, 37 insertions(+), 21 deletions(-) diff --git a/blueprints/weather_bp.py b/blueprints/weather_bp.py index 3f0be11..669fa96 100644 --- a/blueprints/weather_bp.py +++ b/blueprints/weather_bp.py @@ -4,7 +4,8 @@ import requests from lxml import etree -from flask import Blueprint, jsonify +from flask import Blueprint, jsonify, request +from libs.helpers import haversine_distance weather_blueprint = Blueprint('weather', __name__) @@ -36,27 +37,33 @@ def _metar(airport): metar_text = response.content.decode('utf-8') return jsonify([metar_text]) - @weather_blueprint.route('/sigmets') def _get_sigmets(): - response = requests.get( - 'https://aviationweather.gov/api/data/airsigmet?format=xml') - sigmet_list = [] - tree = etree.fromstring(response.content) - for entry in tree.iter('AIRSIGMET'): - try: - sigmet_entry = { - 'text': entry.find('raw_text').text, - 'hazard': dict(entry.find('hazard').attrib), - 'area': [[p.find('longitude').text, p.find('latitude').text] for p in entry.find('area').iter('point')], - 'altitude': dict(entry.find('altitude').attrib), - 'airsigmet_type': entry.find('airsigmet_type').text, - } - sigmet_list.append(sigmet_entry) - except Exception as e: - pass - # logging.Logger(str(e)) - return jsonify(sigmet_list) + centerlat = request.args.get('centerLat') + centerlon = request.args.get('centerLon') + + response = requests.get( + 'https://aviationweather.gov/api/data/airsigmet?format=json&types=sigmet').json() + + # Only filter if we have both lat and lon + if centerlat and centerlon: + centerlat = float(centerlat) + centerlon = float(centerlon) + + # Filter sigmets within 150nm + filtered_sigmets = [] + for sigmet in response: + # Check if any point in sigmet polygon is within range + coords = sigmet.get('coords', []) + for coord in coords: + dist = haversine_distance(centerlat, centerlon, + coord['lat'], coord['lon']) + if dist <= 150: + filtered_sigmets.append(sigmet) + break + return jsonify(filtered_sigmets) + + return jsonify(response) @weather_blueprint.route('/datis/airport/') diff --git a/libs/helpers.py b/libs/helpers.py index 34eeeb2..8b45ab1 100644 --- a/libs/helpers.py +++ b/libs/helpers.py @@ -1,5 +1,5 @@ import re - +from math import radians, sin, cos, sqrt, atan2 def matches_airway_format(s: str) -> bool: return bool(re.match(r'^[A-Z]{1,2}\d+$', s)) @@ -31,3 +31,12 @@ def matches_deg_min_lat_lon_format(s: str) -> bool: def matches_any_custom_fix_format(s: str) -> bool: return matches_frd_format(s) or matches_deg_only_lat_lon_format(s) or matches_deg_min_lat_lon_format(s) + +def haversine_distance(lat1, lon1, lat2, lon2): + R = 3440.065 # Earth radius in nautical miles + lat1, lon1, lat2, lon2 = map(radians, [lat1, lon1, lat2, lon2]) + dlat = lat2 - lat1 + dlon = lon2 - lon1 + a = sin(dlat/2)**2 + cos(lat1) * cos(lat2) * sin(dlon/2)**2 + c = 2 * atan2(sqrt(a), sqrt(1-a)) + return R * c From 806931542d06cf48fc7eb15b7aef6808b19176c6 Mon Sep 17 00:00:00 2001 From: Jonah Lefkoff Date: Mon, 18 Aug 2025 10:16:59 -0600 Subject: [PATCH 2/4] get both sigmets and airmets --- blueprints/weather_bp.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blueprints/weather_bp.py b/blueprints/weather_bp.py index 669fa96..6df2fcd 100644 --- a/blueprints/weather_bp.py +++ b/blueprints/weather_bp.py @@ -43,7 +43,7 @@ def _get_sigmets(): centerlon = request.args.get('centerLon') response = requests.get( - 'https://aviationweather.gov/api/data/airsigmet?format=json&types=sigmet').json() + 'https://aviationweather.gov/api/data/airsigmet?format=json').json() # Only filter if we have both lat and lon if centerlat and centerlon: From 5fe1a8b88990b5a82476d80fde825d25c8b96afd Mon Sep 17 00:00:00 2001 From: Jonah Lefkoff Date: Mon, 18 Aug 2025 10:35:44 -0600 Subject: [PATCH 3/4] switch to using artcc id --- blueprints/weather_bp.py | 58 ++++++++++++++++++++++------------------ libs/helpers.py | 9 ------- 2 files changed, 32 insertions(+), 35 deletions(-) diff --git a/blueprints/weather_bp.py b/blueprints/weather_bp.py index 6df2fcd..58eb662 100644 --- a/blueprints/weather_bp.py +++ b/blueprints/weather_bp.py @@ -5,7 +5,8 @@ import requests from lxml import etree from flask import Blueprint, jsonify, request -from libs.helpers import haversine_distance +from shapely.geometry import shape, Point, Polygon +import json weather_blueprint = Blueprint('weather', __name__) @@ -39,32 +40,37 @@ def _metar(airport): @weather_blueprint.route('/sigmets') def _get_sigmets(): - centerlat = request.args.get('centerLat') - centerlon = request.args.get('centerLon') - - response = requests.get( - 'https://aviationweather.gov/api/data/airsigmet?format=json').json() - - # Only filter if we have both lat and lon - if centerlat and centerlon: - centerlat = float(centerlat) - centerlon = float(centerlon) - - # Filter sigmets within 150nm - filtered_sigmets = [] - for sigmet in response: - # Check if any point in sigmet polygon is within range - coords = sigmet.get('coords', []) - for coord in coords: - dist = haversine_distance(centerlat, centerlon, - coord['lat'], coord['lon']) - if dist <= 150: - filtered_sigmets.append(sigmet) - break - return jsonify(filtered_sigmets) - - return jsonify(response) + artcc_id = request.args.get('artcc') + response = requests.get( + 'https://aviationweather.gov/api/data/airsigmet?format=json').json() + + if artcc_id: + # Load ARTCC boundary polygon + with open('resources/ArtccBoundaries.geojson') as f: + geojson = json.load(f) + artcc_poly = None + for feature in geojson['features']: + if feature['properties']['id'].upper() == artcc_id.upper(): + artcc_poly = shape(feature['geometry']) + break + if not artcc_poly: + return jsonify([]) # or handle error + + filtered_sigmets = [] + for sigmet in response: + coords = sigmet.get('coords', []) + for coord in coords: + pt = Point(coord['lon'], coord['lat']) + if artcc_poly.contains(pt): + filtered_sigmets.append(sigmet) + break + # Check distance to polygon boundary + if artcc_poly.exterior.distance(pt) * 60 <= 150: # degrees to nm approx + filtered_sigmets.append(sigmet) + break + return jsonify(filtered_sigmets) + return jsonify(response) @weather_blueprint.route('/datis/airport/') def _get_datis(airport): diff --git a/libs/helpers.py b/libs/helpers.py index 8b45ab1..2ae1e4b 100644 --- a/libs/helpers.py +++ b/libs/helpers.py @@ -31,12 +31,3 @@ def matches_deg_min_lat_lon_format(s: str) -> bool: def matches_any_custom_fix_format(s: str) -> bool: return matches_frd_format(s) or matches_deg_only_lat_lon_format(s) or matches_deg_min_lat_lon_format(s) - -def haversine_distance(lat1, lon1, lat2, lon2): - R = 3440.065 # Earth radius in nautical miles - lat1, lon1, lat2, lon2 = map(radians, [lat1, lon1, lat2, lon2]) - dlat = lat2 - lat1 - dlon = lon2 - lon1 - a = sin(dlat/2)**2 + cos(lat1) * cos(lat2) * sin(dlon/2)**2 - c = 2 * atan2(sqrt(a), sqrt(1-a)) - return R * c From 2881f1265a1bf8fc485667e9151738260cff198c Mon Sep 17 00:00:00 2001 From: Jonah Lefkoff Date: Mon, 18 Aug 2025 10:38:13 -0600 Subject: [PATCH 4/4] add shapely dep --- requirements.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/requirements.txt b/requirements.txt index b2147a8..6118923 100644 --- a/requirements.txt +++ b/requirements.txt @@ -14,7 +14,10 @@ itsdangerous==2.2.0 Jinja2==3.1.6 lxml==5.3.2 MarkupSafe==3.0.2 +numpy==2.3.2 pymongo==4.11.3 +python-dotenv==1.1.1 requests==2.32.3 +shapely==2.1.1 urllib3==2.3.0 Werkzeug==3.1.3