Spaces:
Sleeping
Sleeping
| """ | |
| ============================================================ | |
| Step 1: Analyze & Validate map.geojson | |
| ============================================================ | |
| This script reads the zone data from map.geojson and produces | |
| a comprehensive analysis report including: | |
| - Zone counts (Red vs Yellow) | |
| - Data quality issues (missing names, inconsistent keys) | |
| - Zone area calculations | |
| - Bounding box of the operational area | |
| - Visual map of all zones | |
| ============================================================ | |
| """ | |
| import json | |
| import os | |
| import sys | |
| import geopandas as gpd | |
| import matplotlib.pyplot as plt | |
| import matplotlib.patches as mpatches | |
| from shapely.geometry import shape | |
| # ββββββββββββββββββββββββββββββββββββββββββββββ | |
| # CONFIG | |
| # ββββββββββββββββββββββββββββββββββββββββββββββ | |
| BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) | |
| DATA_DIR = os.path.join(BASE_DIR, "data") | |
| OUTPUT_DIR = os.path.join(BASE_DIR, "output") | |
| MAP_FILE = os.path.join(DATA_DIR, "map.geojson") | |
| def load_geojson(filepath): | |
| """Load GeoJSON file and return raw dict + GeoDataFrame.""" | |
| with open(filepath, "r", encoding="utf-8") as f: | |
| raw = json.load(f) | |
| gdf = gpd.read_file(filepath) | |
| return raw, gdf | |
| def classify_zones(raw_data): | |
| """ | |
| Classify each feature into Red, Yellow, Boundary, or Unknown. | |
| Handles inconsistent property keys (zone_id vs zone-id, type with trailing space). | |
| """ | |
| zones = { | |
| "red": [], | |
| "yellow": [], | |
| "boundary": [], | |
| "empty": [], | |
| "unknown": [] | |
| } | |
| issues = [] | |
| for i, feature in enumerate(raw_data["features"]): | |
| props = feature.get("properties", {}) | |
| # Get zone_id (handle both 'zone_id' and 'zone-id') | |
| zone_id = props.get("zone_id") or props.get("zone-id") | |
| # Get type (handle trailing space: 'type ' vs 'type') | |
| zone_type = props.get("type") or props.get("type ") | |
| if zone_type: | |
| zone_type = zone_type.strip().lower() | |
| name = props.get("name") or props.get("nam") or props.get("name ") | |
| # ββ Check for data issues ββ | |
| if props.get("zone-id") and not props.get("zone_id"): | |
| issues.append(f" β οΈ Feature {i}: Uses 'zone-id' instead of 'zone_id' β {zone_id}") | |
| if props.get("type "): | |
| issues.append(f" β οΈ Feature {i}: 'type ' has trailing space β {zone_id}") | |
| if props.get("nam"): | |
| issues.append(f" β οΈ Feature {i}: Uses 'nam' instead of 'name' β {zone_id}") | |
| if zone_id and not name: | |
| issues.append(f" β οΈ Feature {i}: Missing name for zone β {zone_id}") | |
| # ββ Classify ββ | |
| if not props or len(props) == 0: | |
| zones["empty"].append({"index": i, "zone_id": None, "name": None}) | |
| elif props.get("name") == "Jaipur_Wide_Drone_World": | |
| zones["boundary"].append({"index": i, "zone_id": None, "name": "Operational Boundary"}) | |
| elif zone_type == "red" or (zone_id and "Red" in str(zone_id)): | |
| zones["red"].append({"index": i, "zone_id": zone_id, "name": name or "Unnamed"}) | |
| elif zone_type == "yellow" or (zone_id and "Yellow" in str(zone_id)): | |
| zones["yellow"].append({"index": i, "zone_id": zone_id, "name": name or "Unnamed"}) | |
| elif zone_id: | |
| zones["unknown"].append({"index": i, "zone_id": zone_id, "name": name}) | |
| else: | |
| zones["empty"].append({"index": i, "zone_id": None, "name": None}) | |
| return zones, issues | |
| def calculate_areas(raw_data): | |
| """Calculate area of each zone in square meters (approximate).""" | |
| zone_areas = [] | |
| for feature in raw_data["features"]: | |
| props = feature.get("properties", {}) | |
| zone_id = props.get("zone_id") or props.get("zone-id") | |
| if not zone_id: | |
| continue | |
| try: | |
| geom = shape(feature["geometry"]) | |
| # Convert degrees to approximate meters (at Jaipur latitude ~26.85Β°N) | |
| # 1 degree lat β 111,320 m, 1 degree lon β 99,855 m at this latitude | |
| area_deg2 = geom.area | |
| area_m2 = area_deg2 * 111320 * 99855 # Rough approximation | |
| zone_areas.append({ | |
| "zone_id": zone_id, | |
| "name": props.get("name") or props.get("nam") or "?", | |
| "area_m2": area_m2, | |
| "area_km2": area_m2 / 1e6 | |
| }) | |
| except Exception: | |
| pass | |
| return zone_areas | |
| def get_bounding_box(raw_data): | |
| """Get the operational area bounding box.""" | |
| for feature in raw_data["features"]: | |
| props = feature.get("properties", {}) | |
| if props.get("name") == "Jaipur_Wide_Drone_World": | |
| coords = feature["geometry"]["coordinates"][0] | |
| lons = [c[0] for c in coords] | |
| lats = [c[1] for c in coords] | |
| return { | |
| "min_lon": min(lons), | |
| "max_lon": max(lons), | |
| "min_lat": min(lats), | |
| "max_lat": max(lats), | |
| "width_km": (max(lons) - min(lons)) * 99.855, | |
| "height_km": (max(lats) - min(lats)) * 111.32 | |
| } | |
| return None | |
| def plot_zones(raw_data, zones, output_dir): | |
| """Create a visual map of all zones.""" | |
| fig, ax = plt.subplots(1, 1, figsize=(14, 12)) | |
| ax.set_facecolor("#1a1a2e") | |
| fig.patch.set_facecolor("#0d0d1a") | |
| # Draw boundary | |
| for feature in raw_data["features"]: | |
| props = feature.get("properties", {}) | |
| if props.get("name") == "Jaipur_Wide_Drone_World": | |
| geom = shape(feature["geometry"]) | |
| x, y = geom.exterior.xy | |
| ax.fill(x, y, alpha=0.1, color="#00ff88", linewidth=2, edgecolor="#00ff88") | |
| ax.plot(x, y, color="#00ff88", linewidth=2, linestyle="--", alpha=0.6) | |
| # Draw Red zones | |
| for z in zones["red"]: | |
| feature = raw_data["features"][z["index"]] | |
| geom = shape(feature["geometry"]) | |
| x, y = geom.exterior.xy | |
| ax.fill(x, y, alpha=0.5, color="#ff3333", edgecolor="#ff0000", linewidth=1.5) | |
| cx, cy = geom.centroid.coords[0] | |
| ax.annotate(z["zone_id"], (cx, cy), fontsize=5, ha="center", | |
| color="white", fontweight="bold", | |
| bbox=dict(boxstyle="round,pad=0.2", facecolor="#ff0000", alpha=0.7)) | |
| # Draw Yellow zones | |
| for z in zones["yellow"]: | |
| feature = raw_data["features"][z["index"]] | |
| geom = shape(feature["geometry"]) | |
| try: | |
| x, y = geom.exterior.xy | |
| ax.fill(x, y, alpha=0.4, color="#ffcc00", edgecolor="#ff9900", linewidth=1) | |
| cx, cy = geom.centroid.coords[0] | |
| ax.annotate(z["zone_id"], (cx, cy), fontsize=4, ha="center", | |
| color="black", fontweight="bold", | |
| bbox=dict(boxstyle="round,pad=0.15", facecolor="#ffcc00", alpha=0.6)) | |
| except Exception: | |
| pass | |
| # Legend | |
| red_patch = mpatches.Patch(color="#ff3333", alpha=0.6, label=f'π΄ Red Zones ({len(zones["red"])})') | |
| yellow_patch = mpatches.Patch(color="#ffcc00", alpha=0.6, label=f'π‘ Yellow Zones ({len(zones["yellow"])})') | |
| green_patch = mpatches.Patch(color="#00ff88", alpha=0.3, label="π’ Operational Area") | |
| ax.legend(handles=[red_patch, yellow_patch, green_patch], loc="upper left", | |
| fontsize=10, facecolor="#1a1a2e", edgecolor="#00ff88", | |
| labelcolor="white") | |
| ax.set_xlabel("Longitude", color="white", fontsize=12) | |
| ax.set_ylabel("Latitude", color="white", fontsize=12) | |
| ax.set_title("πΈ Jaipur Drone Airspace Map β Zone Analysis", color="#00ff88", | |
| fontsize=16, fontweight="bold", pad=20) | |
| ax.tick_params(colors="white") | |
| for spine in ax.spines.values(): | |
| spine.set_color("#00ff88") | |
| plt.tight_layout() | |
| save_path = os.path.join(output_dir, "zone_analysis_map.png") | |
| plt.savefig(save_path, dpi=150, bbox_inches="tight", facecolor=fig.get_facecolor()) | |
| plt.close() | |
| print(f"\n π Map saved to: {save_path}") | |
| def main(): | |
| print("=" * 60) | |
| print(" πΈ DRONE AIRSPACE MAP ANALYZER β Step 1") | |
| print("=" * 60) | |
| # ββ Load data ββ | |
| if not os.path.exists(MAP_FILE): | |
| print(f"\n β ERROR: map.geojson not found at: {MAP_FILE}") | |
| sys.exit(1) | |
| print(f"\n π Loading: {MAP_FILE}") | |
| raw_data, gdf = load_geojson(MAP_FILE) | |
| print(f" β Loaded {len(raw_data['features'])} features") | |
| # ββ Bounding Box ββ | |
| bbox = get_bounding_box(raw_data) | |
| if bbox: | |
| print(f"\n π Operational Area:") | |
| print(f" Longitude: {bbox['min_lon']:.4f} β {bbox['max_lon']:.4f}") | |
| print(f" Latitude: {bbox['min_lat']:.4f} β {bbox['max_lat']:.4f}") | |
| print(f" Size: {bbox['width_km']:.1f} km Γ {bbox['height_km']:.1f} km") | |
| # ββ Classify zones ββ | |
| zones, issues = classify_zones(raw_data) | |
| print(f"\n π Zone Classification:") | |
| print(f" π΄ Red Zones: {len(zones['red'])}") | |
| print(f" π‘ Yellow Zones: {len(zones['yellow'])}") | |
| print(f" π’ Boundary: {len(zones['boundary'])}") | |
| print(f" β Empty/Unknown: {len(zones['empty']) + len(zones['unknown'])}") | |
| # ββ List all zones ββ | |
| print(f"\n {'β' * 50}") | |
| print(f" π΄ RED ZONES (No-Fly):") | |
| print(f" {'β' * 50}") | |
| for z in zones["red"]: | |
| print(f" {z['zone_id']:>10s} β {z['name']}") | |
| print(f"\n {'β' * 50}") | |
| print(f" π‘ YELLOW ZONES (Permission Required):") | |
| print(f" {'β' * 50}") | |
| for z in zones["yellow"]: | |
| print(f" {z['zone_id']:>12s} β {z['name']}") | |
| # ββ Data Quality Issues ββ | |
| if issues: | |
| print(f"\n {'β' * 50}") | |
| print(f" β οΈ DATA QUALITY ISSUES ({len(issues)} found):") | |
| print(f" {'β' * 50}") | |
| for issue in issues: | |
| print(issue) | |
| else: | |
| print(f"\n β No data quality issues found!") | |
| # ββ Area calculations ββ | |
| zone_areas = calculate_areas(raw_data) | |
| zone_areas.sort(key=lambda x: x["area_m2"], reverse=True) | |
| print(f"\n {'β' * 50}") | |
| print(f" π TOP 10 LARGEST ZONES BY AREA:") | |
| print(f" {'β' * 50}") | |
| print(f" {'Zone ID':>14s} β {'Name':<30s} β {'Area':>10s}") | |
| print(f" {'β' * 14}ββΌβ{'β' * 30}ββΌβ{'β' * 10}") | |
| for z in zone_areas[:10]: | |
| if z["area_km2"] >= 0.01: | |
| area_str = f"{z['area_km2']:.3f} kmΒ²" | |
| else: | |
| area_str = f"{z['area_m2']:.0f} mΒ²" | |
| print(f" {z['zone_id']:>14s} β {z['name']:<30s} β {area_str:>10s}") | |
| # ββ Plot zones ββ | |
| os.makedirs(OUTPUT_DIR, exist_ok=True) | |
| print(f"\n π¨ Generating zone visualization map...") | |
| plot_zones(raw_data, zones, OUTPUT_DIR) | |
| # ββ Summary ββ | |
| total_red_area = sum(z["area_km2"] for z in zone_areas if "Red" in z["zone_id"]) | |
| total_yellow_area = sum(z["area_km2"] for z in zone_areas if "Yellow" in z["zone_id"]) | |
| total_area = bbox["width_km"] * bbox["height_km"] if bbox else 0 | |
| green_area = total_area - total_red_area - total_yellow_area | |
| print(f"\n {'β' * 50}") | |
| print(f" π SUMMARY") | |
| print(f" {'β' * 50}") | |
| print(f" Total operational area: {total_area:.1f} kmΒ²") | |
| print(f" Red zone coverage: {total_red_area:.3f} kmΒ² ({total_red_area/total_area*100:.1f}%)") | |
| print(f" Yellow zone coverage: {total_yellow_area:.3f} kmΒ² ({total_yellow_area/total_area*100:.1f}%)") | |
| print(f" Green (open) area: {green_area:.1f} kmΒ² ({green_area/total_area*100:.1f}%)") | |
| print(f"\n β Step 1 Complete! Ready for Step 2 (Building Extraction).") | |
| print(f" {'β' * 50}") | |
| if __name__ == "__main__": | |
| main() | |