Spaces:
Sleeping
Sleeping
| """ | |
| ============================================================ | |
| Step 2: Fetch Real Buildings from OpenStreetMap (OSMnx) | |
| ============================================================ | |
| This script: | |
| 1. Downloads building footprints within 10km of Jawahar Circle, Jaipur | |
| 2. Assigns heights using smart modeling: | |
| - Manual overrides for famous buildings | |
| - Floor-to-meter conversion (H = floors × 3.5) | |
| - Stochastic estimation by building type | |
| 3. Filters buildings >= 30m (drone cruise at 60m) | |
| 4. Saves to output/buildings_raw.geojson | |
| ============================================================ | |
| """ | |
| import os | |
| import sys | |
| import json | |
| import random | |
| import warnings | |
| import numpy as np | |
| import geopandas as gpd | |
| warnings.filterwarnings("ignore") | |
| # ────────────────────────────────────────────── | |
| # CONFIG | |
| # ────────────────────────────────────────────── | |
| BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) | |
| OUTPUT_DIR = os.path.join(BASE_DIR, "output") | |
| # Jawahar Circle, Jaipur — center point for building search | |
| CENTER_POINT = (26.8514, 75.8064) # (lat, lon) | |
| SEARCH_RADIUS = 10000 # 10 km radius | |
| # Drone config | |
| MIN_HEIGHT_FILTER = 30 # meters — ignore buildings shorter than this | |
| DRONE_CRUISE_ALT = 60 # meters | |
| # Random seed for reproducibility | |
| random.seed(42) | |
| np.random.seed(42) | |
| # ────────────────────────────────────────────── | |
| # MANUAL HEIGHT OVERRIDES (Famous Jaipur Buildings) | |
| # ────────────────────────────────────────────── | |
| MANUAL_HEIGHTS = { | |
| "world trade park": 45, | |
| "wtp": 45, | |
| "gaurav tower": 40, | |
| "gt central": 40, | |
| "crystal palm": 35, | |
| "crystal court": 35, | |
| "triton mall": 38, | |
| "marriott": 50, | |
| "jw marriott": 50, | |
| "holiday inn": 42, | |
| "radisson": 45, | |
| "hilton": 48, | |
| "clarks amer": 35, | |
| "hotel clarks amer": 35, | |
| "rajmandir cinema": 30, | |
| "pink square mall": 32, | |
| "mgf metropolitan": 35, | |
| "elante mall": 36, | |
| "birla mandir": 35, | |
| "hawa mahal": 15, | |
| "city palace": 20, | |
| "nahargarh fort": 25, | |
| "amber fort": 30, | |
| "jaipur airport": 15, | |
| "sms hospital": 30, | |
| "fortis hospital": 35, | |
| "manipal hospital": 40, | |
| "mahatma gandhi hospital": 32, | |
| } | |
| # ────────────────────────────────────────────── | |
| # HEIGHT ESTIMATION BY BUILDING TYPE | |
| # ────────────────────────────────────────────── | |
| HEIGHT_RANGES = { | |
| "commercial": (25, 50), | |
| "retail": (15, 35), | |
| "office": (20, 45), | |
| "hotel": (25, 55), | |
| "hospital": (20, 40), | |
| "industrial": (12, 30), | |
| "warehouse": (10, 25), | |
| "residential": (8, 25), | |
| "apartments": (15, 40), | |
| "house": (5, 12), | |
| "school": (10, 20), | |
| "university": (12, 25), | |
| "religious": (10, 35), | |
| "temple": (10, 30), | |
| "mosque": (8, 25), | |
| "church": (10, 30), | |
| "civic": (10, 25), | |
| "government": (12, 30), | |
| "public": (10, 25), | |
| "default": (8, 20), | |
| } | |
| def fetch_buildings(): | |
| """Fetch building footprints from OpenStreetMap using OSMnx.""" | |
| try: | |
| import osmnx as ox | |
| except ImportError: | |
| print(" [ERROR] osmnx not installed. Run: pip install osmnx") | |
| sys.exit(1) | |
| print(f" Fetching buildings within {SEARCH_RADIUS/1000:.0f}km of Jawahar Circle...") | |
| print(f" Center: ({CENTER_POINT[0]}, {CENTER_POINT[1]})") | |
| print(f" This may take 1-2 minutes...\n") | |
| try: | |
| # Fetch building footprints | |
| tags = {"building": True} | |
| gdf = ox.features_from_point(CENTER_POINT, tags=tags, dist=SEARCH_RADIUS) | |
| # Keep only Polygon and MultiPolygon geometries | |
| gdf = gdf[gdf.geometry.type.isin(["Polygon", "MultiPolygon"])] | |
| print(f" Downloaded {len(gdf)} building footprints from OSM") | |
| return gdf | |
| except Exception as e: | |
| print(f" [ERROR] Failed to fetch buildings: {e}") | |
| print(f" Make sure you have internet connection.") | |
| sys.exit(1) | |
| def get_manual_height(name): | |
| """Check if building name matches any manual override.""" | |
| if not name or not isinstance(name, str): | |
| return None | |
| name_lower = name.lower().strip() | |
| for key, height in MANUAL_HEIGHTS.items(): | |
| if key in name_lower: | |
| return height | |
| return None | |
| def estimate_height(row): | |
| """ | |
| Estimate building height using a priority system: | |
| 1. Manual override (famous buildings) | |
| 2. Explicit height tag from OSM | |
| 3. Floor-to-meter conversion (H = levels x 3.5) | |
| 4. Stochastic estimation by building type | |
| """ | |
| # 1. Check manual overrides | |
| name = row.get("name", "") | |
| manual_h = get_manual_height(name) | |
| if manual_h is not None: | |
| return manual_h, "manual" | |
| # 2. Check OSM height tag | |
| osm_height = row.get("height") | |
| if osm_height is not None: | |
| try: | |
| h = float(str(osm_height).replace("m", "").strip()) | |
| if h > 0: | |
| return h, "osm_height" | |
| except (ValueError, TypeError): | |
| pass | |
| # 3. Floor-to-meter conversion | |
| levels = row.get("building:levels") | |
| if levels is not None: | |
| try: | |
| h = float(str(levels)) * 3.5 | |
| if h > 0: | |
| return h, "floors" | |
| except (ValueError, TypeError): | |
| pass | |
| # 4. Stochastic estimation by building type | |
| building_type = str(row.get("building", "default")).lower() | |
| # Map specific types to our categories | |
| type_mapping = { | |
| "yes": "default", | |
| "true": "default", | |
| "commercial": "commercial", | |
| "retail": "retail", | |
| "office": "office", | |
| "hotel": "hotel", | |
| "hospital": "hospital", | |
| "industrial": "industrial", | |
| "warehouse": "warehouse", | |
| "residential": "residential", | |
| "apartments": "apartments", | |
| "house": "house", | |
| "detached": "house", | |
| "semidetached_house": "house", | |
| "terrace": "house", | |
| "school": "school", | |
| "university": "university", | |
| "college": "university", | |
| "religious": "religious", | |
| "temple": "temple", | |
| "mosque": "mosque", | |
| "church": "church", | |
| "civic": "civic", | |
| "government": "government", | |
| "public": "public", | |
| } | |
| category = type_mapping.get(building_type, "default") | |
| low, high = HEIGHT_RANGES.get(category, HEIGHT_RANGES["default"]) | |
| h = random.uniform(low, high) | |
| return round(h, 1), "estimated" | |
| def process_buildings(gdf): | |
| """Process buildings: assign heights and filter.""" | |
| print(f" Assigning heights to {len(gdf)} buildings...") | |
| heights = [] | |
| methods = [] | |
| names = [] | |
| for idx, row in gdf.iterrows(): | |
| h, method = estimate_height(row) | |
| heights.append(h) | |
| methods.append(method) | |
| names.append(row.get("name", "")) | |
| gdf = gdf.copy() | |
| gdf["height"] = heights | |
| gdf["height_method"] = methods | |
| gdf["building_name"] = names | |
| # Stats before filtering | |
| method_counts = {} | |
| for m in methods: | |
| method_counts[m] = method_counts.get(m, 0) + 1 | |
| print(f"\n Height Assignment Methods:") | |
| for m, c in sorted(method_counts.items(), key=lambda x: -x[1]): | |
| print(f" {m:>12s}: {c:>5d} buildings") | |
| print(f"\n Height Distribution (before filter):") | |
| h_array = np.array(heights) | |
| print(f" Min: {h_array.min():.1f}m") | |
| print(f" Max: {h_array.max():.1f}m") | |
| print(f" Mean: {h_array.mean():.1f}m") | |
| print(f" Median: {np.median(h_array):.1f}m") | |
| # Filter: keep only buildings >= MIN_HEIGHT_FILTER | |
| before_count = len(gdf) | |
| gdf = gdf[gdf["height"] >= MIN_HEIGHT_FILTER] | |
| after_count = len(gdf) | |
| print(f"\n Filtering buildings >= {MIN_HEIGHT_FILTER}m (drone cruise: {DRONE_CRUISE_ALT}m):") | |
| print(f" Before: {before_count}") | |
| print(f" After: {after_count}") | |
| print(f" Removed: {before_count - after_count} short buildings") | |
| return gdf | |
| def save_buildings(gdf, output_path): | |
| """Save processed buildings to GeoJSON with clean columns.""" | |
| # Keep only essential columns | |
| keep_cols = ["geometry", "height", "height_method", "building_name"] | |
| available_cols = [c for c in keep_cols if c in gdf.columns] | |
| gdf_clean = gdf[available_cols].copy() | |
| # Add metadata | |
| gdf_clean["type"] = "building" | |
| gdf_clean["source"] = "osm" | |
| # Reset index for clean output | |
| gdf_clean = gdf_clean.reset_index(drop=True) | |
| # Save | |
| gdf_clean.to_file(output_path, driver="GeoJSON") | |
| file_size = os.path.getsize(output_path) / 1024 | |
| print(f"\n Saved to: {output_path}") | |
| print(f" File size: {file_size:.1f} KB") | |
| print(f" Buildings: {len(gdf_clean)}") | |
| return gdf_clean | |
| def print_top_buildings(gdf, n=15): | |
| """Print the tallest buildings found.""" | |
| gdf_sorted = gdf.sort_values("height", ascending=False).head(n) | |
| print(f"\n {'='*55}") | |
| print(f" TOP {n} TALLEST BUILDINGS:") | |
| print(f" {'='*55}") | |
| print(f" {'#':>3s} | {'Name':<30s} | {'Height':>7s} | {'Method'}") | |
| print(f" {'─'*3}─┼─{'─'*30}─┼─{'─'*7}─┼─{'─'*12}") | |
| for i, (idx, row) in enumerate(gdf_sorted.iterrows(), 1): | |
| name = str(row.get("building_name", ""))[:30] or "Unnamed" | |
| height = f"{row['height']:.1f}m" | |
| method = row.get("height_method", "?") | |
| print(f" {i:>3d} | {name:<30s} | {height:>7s} | {method}") | |
| def main(): | |
| print("=" * 60) | |
| print(" STEP 2: Fetch Real Buildings (OSMnx)") | |
| print("=" * 60) | |
| # Ensure output directory exists | |
| os.makedirs(OUTPUT_DIR, exist_ok=True) | |
| # 1. Fetch buildings from OSM | |
| print("\n [1/3] Downloading building footprints from OpenStreetMap...") | |
| gdf = fetch_buildings() | |
| # 2. Process: assign heights + filter | |
| print(f"\n [2/3] Processing buildings...") | |
| gdf = process_buildings(gdf) | |
| # 3. Save to GeoJSON | |
| output_path = os.path.join(OUTPUT_DIR, "buildings_raw.geojson") | |
| print(f"\n [3/3] Saving processed buildings...") | |
| gdf_clean = save_buildings(gdf, output_path) | |
| # Print top buildings | |
| print_top_buildings(gdf_clean) | |
| # Summary | |
| print(f"\n {'='*55}") | |
| print(f" SUMMARY") | |
| print(f" {'='*55}") | |
| print(f" Buildings fetched from OSM: {len(gdf)}") | |
| print(f" After height filter (>={MIN_HEIGHT_FILTER}m): {len(gdf_clean)}") | |
| print(f" Output file: buildings_raw.geojson") | |
| print(f"\n [OK] Step 2 Complete! Ready for Step 3 (Simulate Buildings).") | |
| print(f" {'='*55}") | |
| if __name__ == "__main__": | |
| main() | |