Compare commits

...

3 Commits

9 changed files with 267 additions and 159 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1,126 +1,195 @@
{% load static %} {% load static %}
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>LDS City</title> <title>LDS City</title>
<script src="https://aframe.io/releases/1.4.0/aframe.min.js"></script> <!-- A-Frame 1.7.0 & environment component -->
<script src="https://raw.githack.com/donmccurdy/aframe-extras/v6.0.0/dist/aframe-extras.min.js"></script> <script src="https://aframe.io/releases/1.7.0/aframe.min.js"></script>
</head> <script src="https://unpkg.com/aframe-environment-component/dist/aframe-environment-component.min.js"></script>
<body>
<a-scene>
<!-- Preload Assets -->
<a-assets>
<img id="skyTexture" src="{% static 'textures/sky.jpg' %}" />
<img id="floorTexture" src="https://cdn.aframe.io/a-painter/images/floor.jpg" />
</a-assets>
<!-- Lighting --> <!-- 1) Simple “look-at” component to face the camera -->
<a-entity light="type: ambient; color: #BBB"></a-entity> <script>
<a-entity light="type: directional; color: #FFF; intensity: 0.6" position="0 1 0"></a-entity> AFRAME.registerComponent('billboard', {
schema: {type: 'selector'},
tick: function () {
if (!this.data) return;
// Make this entity face the camera each frame
this.el.object3D.lookAt(this.data.object3D.position);
}
});
</script>
</head>
<body>
<a-scene shadow="type: pcfsoft"
environment="preset: forest; dressing: trees; groundColor: #777; skyType: gradient; dressingAmount: 20;">
<!-- Camera and Controls --> <!-- Camera & Controls (give it an id for look-at) -->
<a-entity camera look-controls wasd-controls position="0 2 0"></a-entity> <a-entity id="mainCamera" camera look-controls wasd-controls position="0 2 0"></a-entity>
<!-- Sky --> <!-- Optional: Transparent ground plane (comment out if you want only environment ground) -->
<a-sky id="sky" src="#skyTexture" radius="150"></a-sky> <a-plane position="0 -0.1 0" rotation="-90 0 0"
width="200" height="200"
color="#444" opacity="0.3">
</a-plane>
<!-- Ground Plane --> <!-- Buildings -->
<a-plane position="0 -0.1 0" rotation="-90 0 0" width="200" height="200" {% for building in city_data.buildings %}
material="src: #floorTexture; repeat: 100 100;"></a-plane> <a-entity id="{{ building.id }}" status="{{ building.status }}">
<!-- Building geometry -->
<a-box position="{{ building.position_x }} 1 {{ building.position_z }}"
width="{{ building.width }}" height="{{ building.height }}" depth="{{ building.depth }}"
color="{{ building.color }}">
</a-box>
<!-- Buildings Group --> <!-- Label entity: plane + text, billboarded to face camera -->
<a-entity id="buildings-group"> <a-entity position="{{ building.position_x }} 3 {{ building.position_z }}"
{% for building in city_data.buildings %} billboard="#mainCamera">
<a-entity id="building{{ building.id }}" status="{{ building.status }}" <!-- Semi-transparent background plane -->
position="{{ building.position_x }} 1 {{ building.position_z }}" <a-plane width="4" height="1.2" color="#000" opacity="0.5"></a-plane>
event-set__mouseenter="_event: mouseenter; scale: 1.1 1.1 1.1" <!-- Text in front of plane -->
event-set__mouseleave="_event: mouseleave; scale: 1 1 1">
<a-box width="{{ building.width }}" height="{{ building.height }}" depth="{{ building.depth }}"
color="{{ building.color }}"></a-box>
<a-text value="Status: {{ building.status }}" <a-text value="Status: {{ building.status }}"
position="0 {{ building.height|add:'1' }} 0" width="4"
scale="1.5 1.5 1.5"></a-text> align="center"
color="#FFF"
position="0 0 0.01">
</a-text>
</a-entity> </a-entity>
{% endfor %} </a-entity>
</a-entity> {% endfor %}
<!-- Lamps Group --> <!-- Lamps -->
<a-entity id="lamps-group"> {% for lamp in city_data.lamps %}
{% for lamp in city_data.lamps %} <a-entity id="{{ lamp.id }}" status="{{ lamp.status }}" position="{{ lamp.position_x }} 1 {{ lamp.position_z }}">
<a-entity id="lamp{{ lamp.id }}" status="{{ lamp.status }}" <!-- Lamp geometry -->
position="{{ lamp.position_x }} 1 {{ lamp.position_z }}" <a-cone radius-bottom="0.1" radius-top="0.5"
event-set__mouseenter="_event: mouseenter; scale: 1.1 1.1 1.1" height="{{ lamp.height }}" color="{{ lamp.color }}">
event-set__mouseleave="_event: mouseleave; scale: 1 1 1"> </a-cone>
<a-cone radius-bottom="0.1" radius-top="0.5" height="{{ lamp.height }}" <a-sphere radius="0.2" color="#FFFFFF"
color="{{ lamp.color }}"></a-cone> position="0 {{ lamp.height }} 0">
<a-sphere radius="0.2" color="#FFFFFF" position="0 {{ lamp.height }} 0"></a-sphere> </a-sphere>
<!-- Label: billboard to camera -->
<a-entity position="0 3 0" billboard="#mainCamera">
<a-plane width="4" height="1.2" color="#000" opacity="0.5"></a-plane>
<a-text value="Status: {{ lamp.status }}" <a-text value="Status: {{ lamp.status }}"
position="0 {{ lamp.height|add:'1' }} 0" width="4"
scale="1.5 1.5 1.5"></a-text> align="center"
color="#FFF"
position="0 0 0.01">
</a-text>
</a-entity> </a-entity>
{% endfor %} </a-entity>
</a-entity> {% endfor %}
<!-- Trees Group --> <!-- Trees -->
<a-entity id="trees-group"> {% for tree in city_data.trees %}
{% for tree in city_data.trees %} <a-entity id="{{ tree.id }}" status="{{ tree.status }}" position="{{ tree.position_x }} 1 {{ tree.position_z }}">
<a-entity id="tree{{ tree.id }}" status="{{ tree.status }}" <!-- Tree trunk & leaves -->
position="{{ tree.position_x }} 1 {{ tree.position_z }}" <a-cone radius-bottom="{{ tree.radius_bottom }}"
event-set__mouseenter="_event: mouseenter; scale: 1.1 1.1 1.1" radius-top="{{ tree.radius_top }}"
event-set__mouseleave="_event: mouseleave; scale: 1 1 1"> height="{{ tree.height }}"
<a-cone radius-bottom="{{ tree.radius_bottom }}" radius-top="{{ tree.radius_top }}" color="{{ tree.color_trunk }}">
height="{{ tree.height }}" color="{{ tree.color_trunk }}"></a-cone> </a-cone>
<a-sphere radius="{{ tree.radius_top }}" color="{{ tree.color_leaves }}" <a-sphere radius="{{ tree.radius_top }}"
position="0 {{ tree.height }} 0"></a-sphere> color="{{ tree.color_leaves }}"
position="0 {{ tree.height }} 0">
</a-sphere>
<!-- Label: billboard to camera -->
<a-entity position="0 3 0" billboard="#mainCamera">
<a-plane width="4" height="1.2" color="#000" opacity="0.5"></a-plane>
<a-text value="Status: {{ tree.status }}" <a-text value="Status: {{ tree.status }}"
position="0 {{ tree.height|add:'1' }} 0" width="4"
scale="1.5 1.5 1.5"></a-text> align="center"
color="#FFF"
position="0 0 0.01">
</a-text>
</a-entity> </a-entity>
{% endfor %} </a-entity>
</a-entity> {% endfor %}
<!-- Cell Towers Group --> <!-- Cell Towers -->
<a-entity id="towers-group"> {% for tower in city_data.towers %}
{% for tower in city_data.towers %} <a-entity id="tower{{ tower.id }}"
<a-entity id="tower{{ tower.id }}" position="{{ tower.position_x }} 0 {{ tower.position_z }}" position="{{ tower.position_x }} {{ tower.position_y }} {{ tower.position_z }}">
event-set__mouseenter="_event: mouseenter; scale: 1.1 1.1 1.1" <!-- Base tower cylinder -->
event-set__mouseleave="_event: mouseleave; scale: 1 1 1"> <a-cylinder height="{{ tower.height }}" radius="1" color="{{ tower.color }}"></a-cylinder>
<a-cylinder height="{{ tower.height }}" radius="2" color="{{ tower.color }}"></a-cylinder>
<!-- Animated signal ring near top -->
<a-ring color="#FF0000"
radius-inner="2"
radius-outer="2.5"
position="0 {{ tower.height|add:'1' }} 0"
rotation="-90 0 0"
animation="property: scale; to: 1.5 1.5 1.5; dir: alternate; dur: 1000; loop: true">
</a-ring>
<!-- Tower label: billboard to camera -->
<a-entity position="0 -5 0" billboard="#mainCamera">
<a-plane width="4" height="1.2" color="#000" opacity="0.5"></a-plane>
<a-text value="📡 Tower {{ tower.id }} - {{ tower.status }}" <a-text value="📡 Tower {{ tower.id }} - {{ tower.status }}"
position="0 {{ tower.height|add:'1' }} 0" scale="1.5 1.5 1.5"></a-text> width="4"
align="center"
color="#FFF"
position="0 0 0.01">
</a-text>
</a-entity> </a-entity>
{% endfor %} </a-entity>
</a-entity> {% endfor %}
<!-- Fiber Paths Group --> <!-- Fiber Paths (Cylinders) -->
<a-entity id="fiber-paths-group"> {% for fiber in city_data.fiber_paths %}
{% for fiber in city_data.fiber_paths %} <a-entity>
<a-entity id="fiber{{ fiber.id }}"> <a-cylinder position="{{ fiber.mid_x }} {{ fiber.mid_y }} {{ fiber.mid_z }}"
<a-line start="{{ fiber.start_x }} 0 {{ fiber.start_z }}" height="{{ fiber.length }}"
end="{{ fiber.end_x }} 0 {{ fiber.end_z }}" radius="0.1"
color="{{ fiber.color }}"></a-line> rotation="90 {{ fiber.angle }} 0"
color="{{ fiber.color }}">
</a-cylinder>
<!-- Fiber label: billboard to camera -->
<a-entity position="{{ fiber.start_x }} 3 {{ fiber.start_z }}" billboard="#mainCamera">
<a-plane width="4" height="1.2" color="#000" opacity="0.5"></a-plane>
<a-text value="🔗 Fiber Path {{ fiber.id }} - {{ fiber.status }}" <a-text value="🔗 Fiber Path {{ fiber.id }} - {{ fiber.status }}"
position="{{ fiber.start_x }} 1 {{ fiber.start_z }}" width="4"
scale="1.2 1.2 1.2"></a-text> align="center"
color="{{ fiber.color }}"
position="0 0 0.01">
</a-text>
</a-entity> </a-entity>
{% endfor %} </a-entity>
</a-entity> {% endfor %}
<!-- Wi-Fi Hotspots Group --> <!-- Wi-Fi Hotspots -->
<a-entity id="wifi-group"> {% for wifi in city_data.wifi_hotspots %}
{% for wifi in city_data.wifi_hotspots %} <a-entity id="wifi{{ wifi.id }}"
<a-entity id="wifi{{ wifi.id }}" position="{{ wifi.position_x }} 0 {{ wifi.position_z }}" position="{{ wifi.position_x }} {{ wifi.position_y }} {{ wifi.position_z }}">
event-set__mouseenter="_event: mouseenter; scale: 1.1 1.1 1.1" <!-- Hotspot sphere (animated) -->
event-set__mouseleave="_event: mouseleave; scale: 1 1 1"> <a-sphere radius="{{ wifi.radius }}" color="{{ wifi.color }}"
<a-sphere radius="2" color="{{ wifi.color }}"></a-sphere> animation="property: scale; to: 1.5 1.5 1.5; dir: alternate; dur: 1500; loop: true">
</a-sphere>
<!-- Coverage area (fixed or dynamic) -->
<a-sphere radius="5"
color="#00FFFF"
opacity="0.2"
position="0 {{ wifi.radius }} 0">
</a-sphere>
<!-- Wi-Fi label: billboard to camera -->
<a-entity position="0 3 0" billboard="#mainCamera">
<a-plane width="4" height="1.2" color="#000" opacity="0.5"></a-plane>
<a-text value="📶 WiFi {{ wifi.id }} - {{ wifi.status }}" <a-text value="📶 WiFi {{ wifi.id }} - {{ wifi.status }}"
position="0 3 0" scale="1.2 1.2 1.2"></a-text> width="4"
align="center"
color="#FFF"
position="0 0 0.01">
</a-text>
</a-entity> </a-entity>
{% endfor %} </a-entity>
</a-entity> {% endfor %}
</a-scene> </a-scene>
<script src="{% static 'js/lds_city_vr.js' %}"></script> </body>
</body>
</html> </html>

151
views.py
View File

@ -3,19 +3,35 @@ from django.http import Http404
import random import random
import math import math
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): def city_digital_twin(request, city_id, innovation_pct=None, technology_pct=None, science_pct=None):
try: try:
if city_id == "random_city": lat = float(request.GET.get('lat', 0))
long = float(request.GET.get('long', 0))
if city_id == "com_con":
city_data = generate_com_con_city_data(lat, long)
elif city_id == "random_city":
city_data = generate_random_city_data() city_data = generate_random_city_data()
elif city_id == "com_con":
city_data = generate_com_con_city_data()
elif city_id == "dream": elif city_id == "dream":
# Retrieve percentages from GET parameters if not in URL
innovation_pct = innovation_pct or request.GET.get('innovation', 0) innovation_pct = innovation_pct or request.GET.get('innovation', 0)
technology_pct = technology_pct or request.GET.get('technology', 0) technology_pct = technology_pct or request.GET.get('technology', 0)
science_pct = science_pct or request.GET.get('science', 0) science_pct = science_pct or request.GET.get('science', 0)
# Convert to integers
innovation_pct = int(innovation_pct) innovation_pct = int(innovation_pct)
technology_pct = int(technology_pct) technology_pct = int(technology_pct)
science_pct = int(science_pct) science_pct = int(science_pct)
@ -23,22 +39,22 @@ def city_digital_twin(request, city_id, innovation_pct=None, technology_pct=None
city_data = generate_random_city_data(innovation_pct, technology_pct, science_pct) city_data = generate_random_city_data(innovation_pct, technology_pct, science_pct)
else: else:
city_data = get_city_data(city_id) city_data = get_city_data(city_id)
except ValueError:
raise Http404("City data not found.")
if not city_data: if not city_data:
city_data = get_example_data() city_data = get_example_data()
context = {'city_data': city_data} preset = get_environment_preset(lat, long)
return render(request, 'pxy_city_digital_twins/city_digital_twin.html', context)
context = {
'city_data': city_data,
'environment_preset': preset,
'lat': lat,
'long': long,
}
return render(request, 'pxy_city_digital_twins/city_digital_twin.html', context)
except (ValueError, TypeError):
raise Http404("Invalid data provided.")
if not city_data:
# Fallback to example data if no city data is found
city_data = get_example_data()
context = {'city_data': city_data}
return render(request, 'pxy_city_digital_twins/city_digital_twin.html', context)
def get_city_data(city_id): def get_city_data(city_id):
# Implement fetching logic here # Implement fetching logic here
@ -244,52 +260,75 @@ def generate_random_city_data(innovation_pct=100, technology_pct=100, science_pc
} }
def generate_com_con_city_data(): def get_environment_by_lat(lat):
""" if lat > 60 or lat < -60:
Generates a telecom-focused digital twin for the hackathon. return 'yeti'
It includes cell towers, fiber paths, and optimized coverage zones. elif 30 < lat < 60 or -30 > lat > -60:
""" return 'forest'
num_towers = random.randint(3, 10) else:
num_fiber_paths = random.randint(5, 15) return 'desert'
num_wifi_hotspots = random.randint(5, 20)
towers = [
{
'id': i + 1,
'status': 'Active' if random.random() > 0.2 else 'Inactive',
'position_x': random.uniform(-50, 50),
'position_z': random.uniform(-50, 50),
'height': random.randint(20, 50),
'range': random.randint(500, 1500), # Coverage range in meters
'color': '#ff4500' # Orange for telecom towers
}
for i in range(num_towers)
]
fiber_paths = [ 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
fiber_paths = []
for i in range(len(towers) - 1):
fiber_paths.append({
'id': i + 1, 'id': i + 1,
'start_x': random.uniform(-50, 50), 'start_x': towers[i]['position_x'],
'start_z': random.uniform(-50, 50), 'start_z': towers[i]['position_z'],
'end_x': random.uniform(-50, 50), 'end_x': towers[i + 1]['position_x'],
'end_z': random.uniform(-50, 50), 'end_z': towers[i + 1]['position_z'],
'mid_x': (towers[i]['position_x'] + towers[i + 1]['position_x']) / 2,
'mid_y': 0.1,
'mid_z': (towers[i]['position_z'] + towers[i + 1]['position_z']) / 2,
'length': ((towers[i + 1]['position_x'] - towers[i]['position_x'])**2 + (towers[i + 1]['position_z'] - towers[i]['position_z'])**2)**0.5,
'angle': random.uniform(0, 360),
'status': 'Connected' if random.random() > 0.1 else 'Broken', 'status': 'Connected' if random.random() > 0.1 else 'Broken',
'color': '#4682b4' # Steel blue for fiber cables 'color': '#4682b4'
} })
for i in range(num_fiber_paths)
]
wifi_hotspots = [ # 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, 'id': i + 1,
'position_x': random.uniform(-50, 50), 'position_x': x,
'position_z': random.uniform(-50, 50), 'position_y': 1.5,
'position_z': z,
'status': 'Online' if random.random() > 0.2 else 'Offline', 'status': 'Online' if random.random() > 0.2 else 'Offline',
'range': random.randint(100, 300), 'radius': random.randint(1, 3),
'color': '#32cd32' # Lime green for Wi-Fi coverage 'color': '#32cd32'
} })
for i in range(num_wifi_hotspots)
]
return { return {
'towers': towers, 'towers': towers,