import osmnx as ox import shapely import random import uuid import networkx as nx from matplotlib import cm import math from shapely.geometry import LineString, MultiLineString def generate_osm_city_data(lat, lon, dist=400, scale=1.0): print(f"πŸ™οΈ Fetching OSM buildings and network at ({lat}, {lon})") scale_factor = scale status_options = ["OK", "Warning", "Critical", "Offline"] road_types = { "motorway", "trunk", "primary", "secondary", "tertiary", "residential", "unclassified", "service", "living_street", } road_widths = { "motorway": 8.0, "trunk": 7.0, "primary": 6.0, "secondary": 5.0, "tertiary": 4.0, "residential": 3.0, } road_colors = { "motorway": "#00ffff", "trunk": "#00ff99", "primary": "#ff00ff", "secondary": "#ff8800", "tertiary": "#ffe600", "residential": "#00aaff", "unclassified": "#66ffcc", "service": "#ff66cc", "living_street": "#66ff66", } default_road_width = 3.0 min_segment_len = 1.0 # β€”β€”β€”β€”β€” STREET NETWORK β€”β€”β€”β€”β€” G = ox.graph_from_point((lat, lon), dist=dist, network_type='drive').to_undirected() degree = dict(G.degree()) max_degree = max(degree.values()) if degree else 1 color_map = cm.get_cmap("plasma") nodes_gdf, edge_gdf = ox.graph_to_gdfs(G, nodes=True, edges=True) nodes_gdf = nodes_gdf.to_crs(epsg=3857) edge_gdf = edge_gdf.to_crs(epsg=3857) # β€”β€”β€”β€”β€” BUILDINGS β€”β€”β€”β€”β€” tags = {"building": True} gdf = ox.features_from_point((lat, lon), tags=tags, dist=dist) gdf = gdf[gdf.geometry.type.isin(["Polygon", "MultiPolygon"])].to_crs(epsg=3857) gdf["centroid"] = gdf.geometry.centroid raw_buildings = [] for i, row in gdf.iterrows(): centroid = row["centroid"] polygon = row["geometry"] building_id = f"BLD-{uuid.uuid4().hex[:6].upper()}" status = random.choice(status_options) try: height = float(row.get("height", None)) except: height = float(row.get("building:levels", 3)) * 3.2 if row.get("building:levels") else 10.0 try: node = ox.distance.nearest_nodes(G, X=centroid.x, Y=centroid.y) node_degree = degree.get(node, 0) except: node_degree = 0 norm_value = node_degree / max_degree rgba = color_map(norm_value) hex_color = '#%02x%02x%02x' % tuple(int(c * 255) for c in rgba[:3]) raw_buildings.append({ "id": building_id, "raw_x": centroid.x, "raw_z": centroid.y, "width": polygon.bounds[2] - polygon.bounds[0], "depth": polygon.bounds[3] - polygon.bounds[1], "height": height, "color": hex_color, "status": status, }) raw_roads = [] raw_road_paths = [] for _, row in edge_gdf.iterrows(): hwy = row.get("highway") if isinstance(hwy, (list, tuple)) and hwy: hwy = hwy[0] if hwy and hwy not in road_types: continue geom = row.get("geometry") lines = [] if isinstance(geom, LineString): lines = [geom] elif isinstance(geom, MultiLineString): lines = list(geom.geoms) else: u = row.get("u") v = row.get("v") if u is not None and v is not None and u in nodes_gdf.index and v in nodes_gdf.index: nu = nodes_gdf.loc[u].geometry nv = nodes_gdf.loc[v].geometry if nu is not None and nv is not None: lines = [LineString([(nu.x, nu.y), (nv.x, nv.y)])] if not lines: continue width = road_widths.get(hwy, default_road_width) color = road_colors.get(hwy, "#666666") for line in lines: coords = list(line.coords) if len(coords) >= 2: raw_road_paths.append({ "coords": coords, "width": width, "color": color, }) for i in range(len(coords) - 1): x1, z1 = coords[i] x2, z2 = coords[i + 1] raw_roads.append({ "raw_x1": x1, "raw_z1": z1, "raw_x2": x2, "raw_z2": z2, "width": width, "color": color, }) # β€”β€”β€”β€”β€” CENTER AND SCALE β€”β€”β€”β€”β€” if raw_buildings: avg_x = sum(b['raw_x'] for b in raw_buildings) / len(raw_buildings) avg_z = sum(b['raw_z'] for b in raw_buildings) / len(raw_buildings) else: avg_x = 0.0 avg_z = 0.0 if not raw_buildings and raw_roads: avg_x = sum((r["raw_x1"] + r["raw_x2"]) / 2 for r in raw_roads) / len(raw_roads) avg_z = sum((r["raw_z1"] + r["raw_z2"]) / 2 for r in raw_roads) / len(raw_roads) if raw_buildings: buildings = [{ "id": b['id'], "position_x": (b['raw_x'] - avg_x) * scale_factor, "position_z": (b['raw_z'] - avg_z) * scale_factor, "width": b['width'] * scale_factor, "depth": b['depth'] * scale_factor, "height": b['height'] * scale_factor, "color": b['color'], "status": b['status'], } for b in raw_buildings] else: buildings = [] roads = [] for r in raw_roads: x1 = (r["raw_x1"] - avg_x) * scale_factor z1 = (r["raw_z1"] - avg_z) * scale_factor x2 = (r["raw_x2"] - avg_x) * scale_factor z2 = (r["raw_z2"] - avg_z) * scale_factor dx = x2 - x1 dz = z2 - z1 length = math.hypot(dx, dz) if length < min_segment_len * scale_factor: continue yaw = math.degrees(math.atan2(dz, dx)) roads.append({ "x": (x1 + x2) / 2, "z": (z1 + z2) / 2, "x1": x1, "z1": z1, "x2": x2, "z2": z2, "length": length, "width": max(r["width"] * scale_factor, 0.2), "yaw": yaw, "color": r["color"], }) road_paths = [] for rp in raw_road_paths: pts = [{ "x": (x - avg_x) * scale_factor, "z": (z - avg_z) * scale_factor, } for x, z in rp["coords"]] if len(pts) < 2: continue if len(pts) > 200: last_pt = pts[-1] step = max(1, int(len(pts) / 200)) pts = pts[::step] if pts[-1] != last_pt: pts.append(last_pt) road_paths.append({ "points": pts, "width": max(rp["width"] * scale_factor, 0.2), "color": rp["color"], }) if roads: xs = [r["x"] for r in roads] zs = [r["z"] for r in roads] print( "πŸ›£οΈ Roads: segments=%d bbox=(%.1f, %.1f)-(%.1f, %.1f)" % (len(roads), min(xs), min(zs), max(xs), max(zs)) ) return {"buildings": buildings, "roads": roads, "road_paths": road_paths} def generate_osm_road_midpoints_only(lat, lon, dist=400, scale=1.0): import osmnx as ox import networkx as nx from shapely.geometry import LineString import geopandas as gpd import random print(f"πŸ›£οΈ Fetching road midpoints only at ({lat}, {lon})") # Obtener grafo y convertir a GeoDataFrame con geometrΓ­a G = ox.graph_from_point((lat, lon), dist=dist, network_type='drive').to_undirected() edge_gdf = ox.graph_to_gdfs(G, nodes=False, edges=True) # Proyectar a metros (3857) edge_gdf = edge_gdf.to_crs(epsg=3857) midpoints_raw = [] for _, row in edge_gdf.iterrows(): geom = row.get('geometry', None) if isinstance(geom, LineString) and geom.length > 0: midpoint = geom.interpolate(0.5, normalized=True) midpoints_raw.append((midpoint.x, midpoint.y)) if not midpoints_raw: return {"roads": []} avg_x = sum(x for x, _ in midpoints_raw) / len(midpoints_raw) avg_z = sum(z for _, z in midpoints_raw) / len(midpoints_raw) midpoints = [{ "id": f"RD-{i}", "position_x": (x - avg_x) * scale, "position_z": (z - avg_z) * scale, "status": random.choice(["OK", "Warning", "Critical", "Offline"]) } for i, (x, z) in enumerate(midpoints_raw)] # DEBUG for i in range(min(5, len(midpoints))): print(f"🧭 Road {i}: x={midpoints[i]['position_x']:.2f}, z={midpoints[i]['position_z']:.2f}") return {"roads": midpoints}