diff --git a/__pycache__/urls.cpython-310.pyc b/__pycache__/urls.cpython-310.pyc index 4227ce1..eadc07c 100644 Binary files a/__pycache__/urls.cpython-310.pyc and b/__pycache__/urls.cpython-310.pyc differ diff --git a/__pycache__/views.cpython-310.pyc b/__pycache__/views.cpython-310.pyc index 8d7cdb4..2c7d80d 100644 Binary files a/__pycache__/views.cpython-310.pyc and b/__pycache__/views.cpython-310.pyc differ diff --git a/pxy_city_digital_twins b/pxy_city_digital_twins new file mode 160000 index 0000000..eaf0a62 --- /dev/null +++ b/pxy_city_digital_twins @@ -0,0 +1 @@ +Subproject commit eaf0a6273ab5924dd1de9460e739d68559b92c5d diff --git a/services/__init__.py b/services/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/services/__pycache__/__init__.cpython-310.pyc b/services/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000..7f88406 Binary files /dev/null and b/services/__pycache__/__init__.cpython-310.pyc differ diff --git a/services/__pycache__/com_con_city.cpython-310.pyc b/services/__pycache__/com_con_city.cpython-310.pyc new file mode 100644 index 0000000..147fc1e Binary files /dev/null and b/services/__pycache__/com_con_city.cpython-310.pyc differ diff --git a/services/__pycache__/layouts.cpython-310.pyc b/services/__pycache__/layouts.cpython-310.pyc new file mode 100644 index 0000000..1eb96de Binary files /dev/null and b/services/__pycache__/layouts.cpython-310.pyc differ diff --git a/services/__pycache__/network.cpython-310.pyc b/services/__pycache__/network.cpython-310.pyc new file mode 100644 index 0000000..42c5403 Binary files /dev/null and b/services/__pycache__/network.cpython-310.pyc differ diff --git a/services/__pycache__/osm_city.cpython-310.pyc b/services/__pycache__/osm_city.cpython-310.pyc new file mode 100644 index 0000000..edb0d63 Binary files /dev/null and b/services/__pycache__/osm_city.cpython-310.pyc differ diff --git a/services/__pycache__/presets.cpython-310.pyc b/services/__pycache__/presets.cpython-310.pyc new file mode 100644 index 0000000..e009ef7 Binary files /dev/null and b/services/__pycache__/presets.cpython-310.pyc differ diff --git a/services/__pycache__/random_city.cpython-310.pyc b/services/__pycache__/random_city.cpython-310.pyc new file mode 100644 index 0000000..51e1dea Binary files /dev/null and b/services/__pycache__/random_city.cpython-310.pyc differ diff --git a/services/com_con_city.py b/services/com_con_city.py new file mode 100644 index 0000000..c3b7237 --- /dev/null +++ b/services/com_con_city.py @@ -0,0 +1,155 @@ +import random +from .network import compute_mst_fiber_paths, compute_network_summary + +GRID_SIZE = 5 +SPACING = 15 # Distance between objects + + +def generate_com_con_city_data(lat, long): + """ + Generate a digital twin for a real-world city (e.g., ConcepciΓ³n). + Returns towers, fiber paths, wifi hotspots, and a summary. + """ + random.seed(f"{lat},{long}") + + center_x = lat + center_z = long + + towers = generate_towers(center_x, center_z) + fiber_paths = compute_mst_fiber_paths(towers) + wifi_hotspots = generate_wifi_hotspots(center_x, center_z) + summary = compute_network_summary(towers, fiber_paths, wifi_hotspots) + + return { + 'towers': towers, + 'fiber_paths': fiber_paths, + 'wifi_hotspots': wifi_hotspots, + 'network_summary': summary, + } + +def generate_towers(center_x, center_z, mode="streets"): + """ + Generate towers either in a 'grid' or at realistic 'streets' (mocked). + mode: "grid" | "streets" + """ + if mode == "streets": + return generate_street_corner_towers(center_x, center_z) + else: + return generate_grid_towers(center_x, center_z) + + +import osmnx as ox + +def generate_street_corner_towers(center_x, center_z, min_towers=10): + """ + Get real intersections from OSM and convert them to local x/z positions + relative to center_x / center_z (in meters). Fallbacks to mocked layout if needed. + """ + print("πŸ“ Starting generate_street_corner_towers()") + print(f"β†’ center_x: {center_x}, center_z: {center_z}") + + point = (center_x, center_z) + print(f"β†’ Using real lat/lon: {point}") + + try: + for dist in [100, 200, 500, 1000]: + print(f"πŸ›°οΈ Trying OSM download at radius: {dist} meters...") + G = ox.graph_from_point(point, dist=dist, network_type='all') + G_undirected = G.to_undirected() + degrees = dict(G_undirected.degree()) + intersections = [n for n, d in degrees.items() if d >= 3] + print(f" βœ… Found {len(intersections)} valid intersections.") + if len(intersections) >= min_towers: + break + else: + raise ValueError("No sufficient intersections found.") + + nodes, _ = ox.graph_to_gdfs(G) + origin_lon = nodes.loc[intersections]['x'].mean() + origin_lat = nodes.loc[intersections]['y'].mean() + print(f"πŸ“Œ Using origin_lon: {origin_lon:.6f}, origin_lat: {origin_lat:.6f} for local projection") + + def latlon_to_sim(lon, lat): + dx = (lon - origin_lon) * 111320 + dz = (lat - origin_lat) * 110540 + return center_x + dx, center_z + dz + + towers = [] + for i, node_id in enumerate(intersections): + row = nodes.loc[node_id] + x_sim, z_sim = latlon_to_sim(row['x'], row['y']) + print(f" πŸ—Ό Tower #{i+1} at sim position: x={x_sim:.2f}, z={z_sim:.2f}") + towers.append(make_tower(x_sim, z_sim, i + 1)) + + print(f"βœ… Done. Total towers returned: {len(towers)}\n") + return towers + + except Exception as e: + print(f"❌ OSM tower generation failed: {e}") + print("⚠️ Falling back to mocked tower layout.") + + # Return 3x3 fixed grid as fallback + offsets = [(-30, -30), (-30, 0), (-30, 30), + (0, -30), (0, 0), (0, 30), + (30, -30), (30, 0), (30, 30)] + + towers = [] + for i, (dx, dz) in enumerate(offsets): + x = center_x + dx + z = center_z + dz + towers.append(make_tower(x, z, i + 1)) + + print(f"βœ… Fallback returned {len(towers)} towers.\n") + return towers + + +def generate_grid_towers(center_x, center_z): + """Generates a 5Γ—5 grid of towers around the city center.""" + towers = [] + for i in range(GRID_SIZE): + for j in range(GRID_SIZE): + x = center_x + (i - GRID_SIZE // 2) * SPACING + z = center_z + (j - GRID_SIZE // 2) * SPACING + towers.append({ + 'id': len(towers) + 1, + 'status': 'Active' if random.random() > 0.2 else 'Inactive', + 'position_x': x, + 'position_y': 0, + 'position_z': z, + 'height': random.randint(40, 60), + 'range': random.randint(500, 1000), + 'color': '#ff4500' + }) + return towers + + +def generate_wifi_hotspots(center_x, center_z): + """Places 10 Wi-Fi hotspots randomly around the city center.""" + hotspots = [] + bound = SPACING * GRID_SIZE / 2 + for i in range(10): + x = center_x + random.uniform(-bound, bound) + z = center_z + random.uniform(-bound, bound) + hotspots.append({ + 'id': i + 1, + 'position_x': x, + 'position_y': 1.5, + 'position_z': z, + 'status': 'Online' if random.random() > 0.2 else 'Offline', + 'radius': random.randint(1, 3), + 'color': '#32cd32' + }) + return hotspots + +def make_tower(x, z, id): + return { + 'id': id, + 'status': 'Active', + 'position_x': x, + 'position_y': 0, + 'position_z': z, + 'height': 50, + 'range': 1000, + 'color': '#ff4500' + } + diff --git a/services/layouts.py b/services/layouts.py new file mode 100644 index 0000000..3b00306 --- /dev/null +++ b/services/layouts.py @@ -0,0 +1,45 @@ +import math + +def rectangular_layout(num_elements, max_dimension): + grid_size = int(math.sqrt(num_elements)) + spacing = max_dimension // grid_size + return [ + { + 'position_x': (i % grid_size) * spacing, + 'position_z': (i // grid_size) * spacing + } + for i in range(num_elements) + ] + +def circular_layout(num_elements, radius): + return [ + { + 'position_x': radius * math.cos(2 * math.pi * i / num_elements), + 'position_z': radius * math.sin(2 * math.pi * i / num_elements) + } + for i in range(num_elements) + ] + +def diagonal_layout(num_elements, max_position): + return [ + { + 'position_x': i * max_position // num_elements, + 'position_z': i * max_position // num_elements + } + for i in range(num_elements) + ] + +def triangular_layout(num_elements): + positions = [] + row_length = 1 + while num_elements > 0: + for i in range(row_length): + if num_elements <= 0: + break + positions.append({ + 'position_x': i * 10 - (row_length - 1) * 5, # Spread out each row symmetrically + 'position_z': row_length * 10 + }) + num_elements -= 1 + row_length += 1 + return positions diff --git a/services/network.py b/services/network.py new file mode 100644 index 0000000..4419bec --- /dev/null +++ b/services/network.py @@ -0,0 +1,63 @@ +import networkx as nx +import math + +def compute_distance(t1, t2): + """ + Compute Euclidean distance between two towers in the horizontal plane. + """ + dx = t1['position_x'] - t2['position_x'] + dz = t1['position_z'] - t2['position_z'] + return math.sqrt(dx**2 + dz**2) + +def compute_mst_fiber_paths(towers): + """ + Given a list of tower dictionaries, compute a Minimum Spanning Tree (MST) + and return a list of fiber paths connecting the towers. + """ + G = nx.Graph() + # Add towers as nodes + for tower in towers: + G.add_node(tower['id'], **tower) + + # Add edges: compute pairwise distances + n = len(towers) + for i in range(n): + for j in range(i+1, n): + d = compute_distance(towers[i], towers[j]) + G.add_edge(towers[i]['id'], towers[j]['id'], weight=d) + + # Compute MST + mst = nx.minimum_spanning_tree(G) + + fiber_paths = [] + for edge in mst.edges(data=True): + id1, id2, data = edge + # Find towers corresponding to these IDs + tower1 = next(t for t in towers if t['id'] == id1) + tower2 = next(t for t in towers if t['id'] == id2) + + fiber_paths.append({ + 'id': len(fiber_paths) + 1, + 'start_x': tower1['position_x'], + 'start_z': tower1['position_z'], + 'end_x': tower2['position_x'], + 'end_z': tower2['position_z'], + 'mid_x': (tower1['position_x'] + tower2['position_x']) / 2, + 'mid_y': 0.1, # Slightly above the ground + 'mid_z': (tower1['position_z'] + tower2['position_z']) / 2, + 'length': data['weight'], + # Optionally, compute the angle in degrees if needed: + 'angle': math.degrees(math.atan2(tower2['position_x'] - tower1['position_x'], + tower2['position_z'] - tower1['position_z'])), + 'status': 'Connected', + 'color': '#4682b4' + }) + return fiber_paths + +def compute_network_summary(towers, fiber_paths, wifi_hotspots): + total_fiber = sum(fiber['length'] for fiber in fiber_paths) + return { + 'num_towers': len(towers), + 'total_fiber_length': total_fiber, + 'num_wifi': len(wifi_hotspots), + } \ No newline at end of file diff --git a/services/osm_city.py b/services/osm_city.py new file mode 100644 index 0000000..88ac0fb --- /dev/null +++ b/services/osm_city.py @@ -0,0 +1,70 @@ +import osmnx as ox +import shapely +import random +import uuid + +def generate_osm_city_data(lat, lon, dist=400, scale=1.0): + print(f"πŸ™οΈ Fetching OSM buildings at ({lat}, {lon})") + + scale_factor = scale # Shrinks the city to make the camera look like a giant + + # β€”β€”β€”β€”β€” BUILDINGS β€”β€”β€”β€”β€” + tags = {"building": True} + gdf = ox.features_from_point((lat, lon), tags=tags, dist=dist) + + gdf = gdf[gdf.geometry.type.isin(["Polygon", "MultiPolygon"])].copy() + gdf = gdf.to_crs(epsg=3857) + gdf["centroid"] = gdf.geometry.centroid + + status_options = ["OK", "Warning", "Critical", "Offline"] + raw_buildings = [] + + for i, row in gdf.iterrows(): + centroid = row["centroid"] + polygon = row["geometry"] + + try: + height = float(row.get("height", None)) + except: + try: + height = float(row.get("building:levels", 3)) * 3.2 + except: + height = 10.0 + + building_id = f"BLD-{uuid.uuid4().hex[:6].upper()}" + status = random.choice(status_options) + + 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": "#8a2be2", + "status": status, + }) + + # β€”β€”β€”β€”β€” 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) + + buildings = [] + for b in raw_buildings: + buildings.append({ + "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'], + }) + else: + buildings = [] + + return { + "buildings": buildings, + } diff --git a/services/presets.py b/services/presets.py new file mode 100644 index 0000000..f5685a1 --- /dev/null +++ b/services/presets.py @@ -0,0 +1,25 @@ + + +def get_environment_preset(lat, long): + """ + Determines the A-Frame environment preset based on latitude and longitude. + You can adjust the logic to suit your needs. + """ + # Example logic: adjust these thresholds as needed + if lat >= 60 or lat <= -60: + return 'snow' # Polar regions: snow environment + elif lat >= 30 or lat <= -30: + return 'forest' # Mid-latitudes: forest environment + elif long >= 100: + return 'goldmine' # Arbitrary example: for far east longitudes, a 'goldmine' preset + else: + return 'desert' # Default to desert for lower latitudes and moderate longitudes + + +def get_environment_by_lat(lat): + if lat > 60 or lat < -60: + return 'yeti' + elif 30 < lat < 60 or -30 > lat > -60: + return 'forest' + else: + return 'desert' diff --git a/services/random_city.py b/services/random_city.py new file mode 100644 index 0000000..60d5655 --- /dev/null +++ b/services/random_city.py @@ -0,0 +1,81 @@ +import random +from .layouts import rectangular_layout, circular_layout, diagonal_layout, triangular_layout + + +def generate_random_city_data(innovation_pct=100, technology_pct=100, science_pct=100, max_position=100, radius=50): + num_buildings = random.randint(5, 35) + num_lamps = random.randint(5, 100) + num_trees = random.randint(5, 55) + + # Buildings layout distribution + num_rectangular_buildings = int(num_buildings * innovation_pct / 100) + num_circular_buildings = (num_buildings - num_rectangular_buildings) // 2 + num_triangular_buildings = num_buildings - num_rectangular_buildings - num_circular_buildings + + building_positions = rectangular_layout(num_rectangular_buildings, max_position) + \ + circular_layout(num_circular_buildings, radius) + \ + triangular_layout(num_triangular_buildings) + + # Lamps layout distribution + num_triangular_lamps = int(num_lamps * technology_pct / 100) + num_circular_lamps = (num_lamps - num_triangular_lamps) // 2 + num_diagonal_lamps = num_lamps - num_triangular_lamps - num_circular_lamps + + lamp_positions = triangular_layout(num_triangular_lamps) + \ + circular_layout(num_circular_lamps, radius) + \ + diagonal_layout(num_diagonal_lamps, max_position) + + # Trees layout distribution + num_circular_trees = int(num_trees * science_pct / 100) + num_triangular_trees = (num_trees - num_circular_trees) // 2 + num_diagonal_trees = num_trees - num_circular_trees - num_triangular_trees + + tree_positions = circular_layout(num_circular_trees, radius) + \ + triangular_layout(num_triangular_trees) + \ + diagonal_layout(num_diagonal_trees, max_position) + + buildings = [ + { + 'id': i + 1, + 'status': random.choice(['Occupied', 'Vacant', 'Under Construction']), + 'position_x': pos['position_x'], + 'position_z': pos['position_z'], + 'height': random.randint(10, 50), + 'width': random.randint(5, 20), + 'depth': random.randint(5, 20), + 'color': random.choice(['#8a2be2', '#5f9ea0', '#ff6347', '#4682b4']), + 'file': '' + } for i, pos in enumerate(building_positions) + ] + + lamps = [ + { + 'id': i + 1, + 'status': random.choice(['Functional', 'Non-functional']), + 'position_x': pos['position_x'], + 'position_z': pos['position_z'], + 'height': random.randint(3, 10), + 'color': random.choice(['#ffff00', '#ff0000', '#00ff00']), + } for i, pos in enumerate(lamp_positions) + ] + + trees = [ + { + 'id': i + 1, + 'status': random.choice(['Healthy', 'Diseased', 'Wilting']), + 'position_x': pos['position_x'], + 'position_z': pos['position_z'], + 'height': random.randint(5, 30), + 'radius_bottom': random.uniform(0.1, 0.5), + 'radius_top': random.uniform(0.5, 2.0), + 'color_trunk': '#8b4513', + 'color_leaves': random.choice(['#228b22', '#90ee90', '#8b4513']), + } for i, pos in enumerate(tree_positions) + ] + + return { + 'buildings': buildings, + 'lamps': lamps, + 'trees': trees, + } + diff --git a/templates/pxy_city_digital_twins/_status_gauge.html b/templates/pxy_city_digital_twins/_status_gauge.html new file mode 100644 index 0000000..97c4b6f --- /dev/null +++ b/templates/pxy_city_digital_twins/_status_gauge.html @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + diff --git a/templates/pxy_city_digital_twins/city_digital_twin.html b/templates/pxy_city_digital_twins/city_digital_twin.html index da426db..443c1ee 100644 --- a/templates/pxy_city_digital_twins/city_digital_twin.html +++ b/templates/pxy_city_digital_twins/city_digital_twin.html @@ -3,7 +3,7 @@ - LDS City + Digital Twin City @@ -19,22 +19,33 @@ } }); - - + + + - + @@ -56,19 +67,13 @@ color="{{ building.color }}"> - - - - - - - + + {% include "pxy_city_digital_twins/_status_gauge.html" with ring_color="#00FFFF" offset_y="1.50" status=building.status id=building.id %} + + + {% endfor %} @@ -204,29 +209,6 @@ {% endfor %} - - - - - - - - - - - - - - - - - diff --git a/views.py b/views.py index ed589a8..d7cf6b6 100644 --- a/views.py +++ b/views.py @@ -2,28 +2,29 @@ from django.shortcuts import render, get_object_or_404 from django.http import Http404 import random import math +from .services.presets import get_environment_preset +import networkx as nx +from .services.layouts import ( + rectangular_layout, + circular_layout, + diagonal_layout, + triangular_layout, +) +from .services.random_city import generate_random_city_data +from .services.com_con_city import generate_com_con_city_data +from .services.osm_city import generate_osm_city_data + -def get_environment_preset(lat, long): - """ - Determines the A-Frame environment preset based on latitude and longitude. - You can adjust the logic to suit your needs. - """ - # Example logic: adjust these thresholds as needed - if lat >= 60 or lat <= -60: - return 'snow' # Polar regions: snow environment - elif lat >= 30 or lat <= -30: - return 'forest' # Mid-latitudes: forest environment - elif long >= 100: - return 'goldmine' # Arbitrary example: for far east longitudes, a 'goldmine' preset - else: - return 'desert' # Default to desert for lower latitudes and moderate longitudes def city_digital_twin(request, city_id, innovation_pct=None, technology_pct=None, science_pct=None): try: lat = float(request.GET.get('lat', 0)) long = float(request.GET.get('long', 0)) + scale = float(request.GET.get('scale', 1.0)) # default to 1.0 (normal scale) - if city_id == "com_con": + if city_id == "osm_city": + city_data = generate_osm_city_data(lat, long,scale=scale) + elif city_id == "com_con": city_data = generate_com_con_city_data(lat, long) elif city_id == "random_city": city_data = generate_random_city_data() @@ -134,258 +135,3 @@ def get_example_data(): } ] } - - - -def rectangular_layout(num_elements, max_dimension): - grid_size = int(math.sqrt(num_elements)) - spacing = max_dimension // grid_size - return [ - { - 'position_x': (i % grid_size) * spacing, - 'position_z': (i // grid_size) * spacing - } - for i in range(num_elements) - ] - -def circular_layout(num_elements, radius): - return [ - { - 'position_x': radius * math.cos(2 * math.pi * i / num_elements), - 'position_z': radius * math.sin(2 * math.pi * i / num_elements) - } - for i in range(num_elements) - ] - -def diagonal_layout(num_elements, max_position): - return [ - { - 'position_x': i * max_position // num_elements, - 'position_z': i * max_position // num_elements - } - for i in range(num_elements) - ] - -def triangular_layout(num_elements): - positions = [] - row_length = 1 - while num_elements > 0: - for i in range(row_length): - if num_elements <= 0: - break - positions.append({ - 'position_x': i * 10 - (row_length - 1) * 5, # Spread out each row symmetrically - 'position_z': row_length * 10 - }) - num_elements -= 1 - row_length += 1 - return positions - - -def generate_random_city_data(innovation_pct=100, technology_pct=100, science_pct=100, max_position=100, radius=50): - num_buildings = random.randint(5, 35) - num_lamps = random.randint(5, 100) - num_trees = random.randint(5, 55) - - # Buildings layout distribution - num_rectangular_buildings = int(num_buildings * innovation_pct / 100) - num_circular_buildings = (num_buildings - num_rectangular_buildings) // 2 - num_triangular_buildings = num_buildings - num_rectangular_buildings - num_circular_buildings - - building_positions = rectangular_layout(num_rectangular_buildings, max_position) + \ - circular_layout(num_circular_buildings, radius) + \ - triangular_layout(num_triangular_buildings) - - # Lamps layout distribution - num_triangular_lamps = int(num_lamps * technology_pct / 100) - num_circular_lamps = (num_lamps - num_triangular_lamps) // 2 - num_diagonal_lamps = num_lamps - num_triangular_lamps - num_circular_lamps - - lamp_positions = triangular_layout(num_triangular_lamps) + \ - circular_layout(num_circular_lamps, radius) + \ - diagonal_layout(num_diagonal_lamps, max_position) - - # Trees layout distribution - num_circular_trees = int(num_trees * science_pct / 100) - num_triangular_trees = (num_trees - num_circular_trees) // 2 - num_diagonal_trees = num_trees - num_circular_trees - num_triangular_trees - - tree_positions = circular_layout(num_circular_trees, radius) + \ - triangular_layout(num_triangular_trees) + \ - diagonal_layout(num_diagonal_trees, max_position) - - buildings = [ - { - 'id': i + 1, - 'status': random.choice(['Occupied', 'Vacant', 'Under Construction']), - 'position_x': pos['position_x'], - 'position_z': pos['position_z'], - 'height': random.randint(10, 50), - 'width': random.randint(5, 20), - 'depth': random.randint(5, 20), - 'color': random.choice(['#8a2be2', '#5f9ea0', '#ff6347', '#4682b4']), - 'file': '' - } for i, pos in enumerate(building_positions) - ] - - lamps = [ - { - 'id': i + 1, - 'status': random.choice(['Functional', 'Non-functional']), - 'position_x': pos['position_x'], - 'position_z': pos['position_z'], - 'height': random.randint(3, 10), - 'color': random.choice(['#ffff00', '#ff0000', '#00ff00']), - } for i, pos in enumerate(lamp_positions) - ] - - trees = [ - { - 'id': i + 1, - 'status': random.choice(['Healthy', 'Diseased', 'Wilting']), - 'position_x': pos['position_x'], - 'position_z': pos['position_z'], - 'height': random.randint(5, 30), - 'radius_bottom': random.uniform(0.1, 0.5), - 'radius_top': random.uniform(0.5, 2.0), - 'color_trunk': '#8b4513', - 'color_leaves': random.choice(['#228b22', '#90ee90', '#8b4513']), - } for i, pos in enumerate(tree_positions) - ] - - return { - 'buildings': buildings, - 'lamps': lamps, - 'trees': trees, - } - - -def get_environment_by_lat(lat): - if lat > 60 or lat < -60: - return 'yeti' - elif 30 < lat < 60 or -30 > lat > -60: - return 'forest' - else: - return 'desert' - - -import random - -def generate_com_con_city_data(lat, long): - random.seed(f"{lat},{long}") - - center_x = lat % 100 - center_z = long % 100 - - grid_size = 5 - spacing = 15 # Distance between objects - - # Towers in a grid - towers = [] - for i in range(grid_size): - for j in range(grid_size): - x = center_x + (i - grid_size // 2) * spacing - z = center_z + (j - grid_size // 2) * spacing - towers.append({ - 'id': len(towers) + 1, - 'status': 'Active' if random.random() > 0.2 else 'Inactive', - 'position_x': x, - 'position_y': 0, - 'position_z': z, - 'height': random.randint(40, 60), - 'range': random.randint(500, 1000), - 'color': '#ff4500' - }) - - # Fiber paths connect neighboring towers - # Compute optimized fiber paths using MST - fiber_paths = compute_mst_fiber_paths(towers) - - # Wi-Fi Hotspots scattered nearby but within grid bounds - wifi_hotspots = [] - for i in range(10): - x = center_x + random.uniform(-spacing * grid_size / 2, spacing * grid_size / 2) - z = center_z + random.uniform(-spacing * grid_size / 2, spacing * grid_size / 2) - wifi_hotspots.append({ - 'id': i + 1, - 'position_x': x, - 'position_y': 1.5, - 'position_z': z, - 'status': 'Online' if random.random() > 0.2 else 'Offline', - 'radius': random.randint(1, 3), - 'color': '#32cd32' - }) - - network_summary = compute_network_summary(towers, fiber_paths, wifi_hotspots) - - return { - 'towers': towers, - 'fiber_paths': fiber_paths, - 'wifi_hotspots': wifi_hotspots, - 'network_summary': network_summary, - } - - -import networkx as nx -import math - -def compute_distance(t1, t2): - """ - Compute Euclidean distance between two towers in the horizontal plane. - """ - dx = t1['position_x'] - t2['position_x'] - dz = t1['position_z'] - t2['position_z'] - return math.sqrt(dx**2 + dz**2) - -def compute_mst_fiber_paths(towers): - """ - Given a list of tower dictionaries, compute a Minimum Spanning Tree (MST) - and return a list of fiber paths connecting the towers. - """ - G = nx.Graph() - # Add towers as nodes - for tower in towers: - G.add_node(tower['id'], **tower) - - # Add edges: compute pairwise distances - n = len(towers) - for i in range(n): - for j in range(i+1, n): - d = compute_distance(towers[i], towers[j]) - G.add_edge(towers[i]['id'], towers[j]['id'], weight=d) - - # Compute MST - mst = nx.minimum_spanning_tree(G) - - fiber_paths = [] - for edge in mst.edges(data=True): - id1, id2, data = edge - # Find towers corresponding to these IDs - tower1 = next(t for t in towers if t['id'] == id1) - tower2 = next(t for t in towers if t['id'] == id2) - - fiber_paths.append({ - 'id': len(fiber_paths) + 1, - 'start_x': tower1['position_x'], - 'start_z': tower1['position_z'], - 'end_x': tower2['position_x'], - 'end_z': tower2['position_z'], - 'mid_x': (tower1['position_x'] + tower2['position_x']) / 2, - 'mid_y': 0.1, # Slightly above the ground - 'mid_z': (tower1['position_z'] + tower2['position_z']) / 2, - 'length': data['weight'], - # Optionally, compute the angle in degrees if needed: - 'angle': math.degrees(math.atan2(tower2['position_x'] - tower1['position_x'], - tower2['position_z'] - tower1['position_z'])), - 'status': 'Connected', - 'color': '#4682b4' - }) - return fiber_paths - -def compute_network_summary(towers, fiber_paths, wifi_hotspots): - total_fiber = sum(fiber['length'] for fiber in fiber_paths) - return { - 'num_towers': len(towers), - 'total_fiber_length': total_fiber, - 'num_wifi': len(wifi_hotspots), - } \ No newline at end of file