371 lines
13 KiB
Python
371 lines
13 KiB
Python
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, generate_osm_road_midpoints_only
|
||
|
||
|
||
|
||
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 == "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()
|
||
elif city_id == "dream":
|
||
innovation_pct = innovation_pct or request.GET.get('innovation', 0)
|
||
technology_pct = technology_pct or request.GET.get('technology', 0)
|
||
science_pct = science_pct or request.GET.get('science', 0)
|
||
|
||
innovation_pct = int(innovation_pct)
|
||
technology_pct = int(technology_pct)
|
||
science_pct = int(science_pct)
|
||
|
||
city_data = generate_random_city_data(innovation_pct, technology_pct, science_pct)
|
||
else:
|
||
city_data = get_city_data(city_id)
|
||
|
||
if not city_data:
|
||
city_data = get_example_data()
|
||
|
||
preset = get_environment_preset(lat, long)
|
||
|
||
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.")
|
||
|
||
|
||
def get_city_data(city_id):
|
||
# Implement fetching logic here
|
||
# This is a mock function to demonstrate fetching logic
|
||
if str(city_id) == "1" or str(city_id) == "123e4567-e89b-12d3-a456-426614174000":
|
||
return {
|
||
# Real data retrieval logic goes here
|
||
}
|
||
return None
|
||
|
||
def get_example_data():
|
||
return {
|
||
'buildings': [
|
||
{
|
||
'id': 1,
|
||
'status': 'Occupied',
|
||
'position_x': 0,
|
||
'height': 10,
|
||
'position_z': 0,
|
||
'width': 5,
|
||
'depth': 5,
|
||
'color': '#8a2be2',
|
||
'file': '', # No file for a simple box representation
|
||
},
|
||
{
|
||
'id': 2,
|
||
'status': 'Vacant',
|
||
'position_x': 10,
|
||
'height': 15,
|
||
'position_z': 10,
|
||
'width': 7,
|
||
'depth': 7,
|
||
'color': '#5f9ea0',
|
||
'file': '', # No file for a simple box representation
|
||
}
|
||
],
|
||
'lamps': [
|
||
{
|
||
'id': 1,
|
||
'status': 'Functional',
|
||
'position_x': 3,
|
||
'position_z': 3,
|
||
'height': 4,
|
||
'color': '#ffff00',
|
||
},
|
||
{
|
||
'id': 2,
|
||
'status': 'Broken',
|
||
'position_x': 8,
|
||
'position_z': 8,
|
||
'height': 4,
|
||
'color': '#ff0000',
|
||
}
|
||
],
|
||
'trees': [
|
||
{
|
||
'id': 1,
|
||
'status': 'Healthy',
|
||
'position_x': 5,
|
||
'position_z': 5,
|
||
'height': 6,
|
||
'radius_bottom': 0.2,
|
||
'radius_top': 1,
|
||
'color_trunk': '#8b4513',
|
||
'color_leaves': '#228b22',
|
||
},
|
||
{
|
||
'id': 2,
|
||
'status': 'Diseased',
|
||
'position_x': 15,
|
||
'position_z': 15,
|
||
'height': 6,
|
||
'radius_bottom': 0.2,
|
||
'radius_top': 1,
|
||
'color_trunk': '#a0522d',
|
||
'color_leaves': '#6b8e23',
|
||
}
|
||
]
|
||
}
|
||
|
||
def city_augmented_digital_twin(request, city_id):
|
||
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
|
||
|
||
if city_id == "osm_city":
|
||
city_data = generate_osm_road_midpoints_only(lat, long, scale=scale)
|
||
elif city_id == "random_city":
|
||
city_data = generate_random_city_data()
|
||
elif city_id == "gauge_ahead":
|
||
# No city data needed, just render the special AR scene
|
||
return render(request, 'pxy_city_digital_twins/spawn_gauge_ahead.html')
|
||
else:
|
||
raise Http404("Unsupported city_id for AR view")
|
||
|
||
preset = get_environment_preset(lat, long)
|
||
|
||
context = {
|
||
'city_data': city_data,
|
||
'environment_preset': preset,
|
||
'lat': lat,
|
||
'long': long,
|
||
}
|
||
return render(request, 'pxy_city_digital_twins/city_augmented_digital_twin.html', context)
|
||
|
||
except (ValueError, TypeError):
|
||
raise Http404("Invalid parameters provided.")
|
||
|
||
|
||
from django.shortcuts import render
|
||
from django.http import Http404
|
||
from .services.waste_routes import get_dispatch_data_for
|
||
import json
|
||
|
||
def waste_route_debug(request, subdivision, vehicle):
|
||
# 1) load all routes for this subdivision
|
||
dispatch_data = get_dispatch_data_for(subdivision)
|
||
if not dispatch_data:
|
||
return render(request, 'pxy_city_digital_twins/waste_route_default.html', {
|
||
'subdivision': subdivision
|
||
})
|
||
|
||
# 2) pick your route by the required `vehicle` path-param
|
||
try:
|
||
selected = next(r for r in dispatch_data if r['route_id'] == vehicle)
|
||
except StopIteration:
|
||
return render(request, 'pxy_city_digital_twins/waste_route_not_found.html', {
|
||
'subdivision': subdivision
|
||
})
|
||
|
||
# 3) derive a “center” latitude/longitude from that route:
|
||
coords = selected.get('coords', [])
|
||
if coords:
|
||
avg_lon = sum(pt[0] for pt in coords) / len(coords)
|
||
avg_lat = sum(pt[1] for pt in coords) / len(coords)
|
||
else:
|
||
steps = selected.get('steps', [])
|
||
avg_lon = sum(s['position'][0] for s in steps) / len(steps)
|
||
avg_lat = sum(s['position'][1] for s in steps) / len(steps)
|
||
|
||
# 4) generate your OSM‐based city around that center
|
||
city_data = generate_osm_city_data(avg_lat, avg_lon)
|
||
|
||
# 5) sanitize building heights (replace NaN or missing with default 10.0)
|
||
default_height = 10.0
|
||
for b in city_data.get('buildings', []):
|
||
h = b.get('height')
|
||
if not isinstance(h, (int, float)) or (isinstance(h, float) and math.isnan(h)):
|
||
b['height'] = default_height
|
||
|
||
# 6) render the VR template
|
||
return render(request, 'pxy_city_digital_twins/waste_route_debug.html', {
|
||
'subdivision': subdivision,
|
||
'vehicle': vehicle,
|
||
'selected_route_json': json.dumps(selected),
|
||
'city_data': city_data,
|
||
})
|
||
|
||
|
||
|
||
import math
|
||
import json
|
||
from django.shortcuts import render
|
||
from pyproj import Transformer
|
||
|
||
from .services.osm_city import generate_osm_city_data
|
||
from .services.waste_routes import get_dispatch_data_for
|
||
|
||
def waste_route(request, subdivision, vehicle):
|
||
"""
|
||
URL: /waste/<subdivision>/<vehicle>/
|
||
Renders a single vehicle's waste‐collection route in VR,
|
||
overlaid on an OSM‐generated city around the route center.
|
||
"""
|
||
# 1) load all routes for this subdivision
|
||
dispatch_data = get_dispatch_data_for(subdivision)
|
||
if not dispatch_data:
|
||
return render(request, 'pxy_city_digital_twins/waste_route_default.html', {
|
||
'subdivision': subdivision
|
||
})
|
||
|
||
# 2) pick your route by the required `vehicle` path-param
|
||
try:
|
||
selected = next(r for r in dispatch_data if r['route_id'] == vehicle)
|
||
except StopIteration:
|
||
return render(request, 'pxy_city_digital_twins/waste_route_not_found.html', {
|
||
'subdivision': subdivision
|
||
})
|
||
|
||
# 3) derive center lon/lat from coords (or fallback to steps)
|
||
coords = selected.get('coords', [])
|
||
if coords:
|
||
avg_lon = sum(pt[0] for pt in coords) / len(coords)
|
||
avg_lat = sum(pt[1] for pt in coords) / len(coords)
|
||
else:
|
||
steps = selected.get('steps', [])
|
||
avg_lon = sum(s['position'][0] for s in steps) / len(steps)
|
||
avg_lat = sum(s['position'][1] for s in steps) / len(steps)
|
||
|
||
# 4) generate your OSM‐based city around that center
|
||
city_data = generate_osm_city_data(avg_lat, avg_lon)
|
||
|
||
# 5) sanitize building heights (replace NaN or missing with default 10.0)
|
||
default_height = 10.0
|
||
for b in city_data.get('buildings', []):
|
||
h = b.get('height')
|
||
if not isinstance(h, (int, float)) or (isinstance(h, float) and math.isnan(h)):
|
||
b['height'] = default_height
|
||
|
||
# 6) project all coords (and steps) to Web Mercator, recenter them
|
||
transformer = Transformer.from_crs("EPSG:4326", "EPSG:3857", always_xy=True)
|
||
# route
|
||
merc = [transformer.transform(lon, lat) for lon, lat in coords]
|
||
if not merc:
|
||
merc = [transformer.transform(*s['position']) for s in selected.get('steps', [])]
|
||
avg_x = sum(x for x, _ in merc) / len(merc)
|
||
avg_z = sum(z for _, z in merc) / len(merc)
|
||
rel_coords = [[x - avg_x, z - avg_z] for x, z in merc]
|
||
|
||
# steps
|
||
step_positions = []
|
||
for step in selected.get('steps', []):
|
||
lon, lat = step['position']
|
||
x, z = transformer.transform(lon, lat)
|
||
step_positions.append({
|
||
'x': x - avg_x,
|
||
'z': z - avg_z,
|
||
'step': step
|
||
})
|
||
|
||
# 7) render
|
||
return render(request, 'pxy_city_digital_twins/virtual_waste_route.html', {
|
||
'subdivision': subdivision,
|
||
'vehicle': vehicle,
|
||
'selected_route_json': json.dumps(selected),
|
||
'city_data': city_data,
|
||
'rel_coords': rel_coords,
|
||
'step_positions': step_positions,
|
||
})
|
||
|
||
def augmented_waste_route(request, subdivision, vehicle):
|
||
"""
|
||
URL: /waste/<subdivision>/<vehicle>/
|
||
Renders a single vehicle's waste‐collection route in VR,
|
||
overlaid on an OSM‐generated city around the route center.
|
||
"""
|
||
# 1) load all routes for this subdivision
|
||
dispatch_data = get_dispatch_data_for(subdivision)
|
||
if not dispatch_data:
|
||
return render(request, 'pxy_city_digital_twins/waste_route_default.html', {
|
||
'subdivision': subdivision
|
||
})
|
||
|
||
# 2) pick your route by the required `vehicle` path-param
|
||
try:
|
||
selected = next(r for r in dispatch_data if r['route_id'] == vehicle)
|
||
except StopIteration:
|
||
return render(request, 'pxy_city_digital_twins/waste_route_not_found.html', {
|
||
'subdivision': subdivision
|
||
})
|
||
|
||
# 3) derive center lon/lat from coords (or fallback to steps)
|
||
coords = selected.get('coords', [])
|
||
if coords:
|
||
avg_lon = sum(pt[0] for pt in coords) / len(coords)
|
||
avg_lat = sum(pt[1] for pt in coords) / len(coords)
|
||
else:
|
||
steps = selected.get('steps', [])
|
||
avg_lon = sum(s['position'][0] for s in steps) / len(steps)
|
||
avg_lat = sum(s['position'][1] for s in steps) / len(steps)
|
||
|
||
# 4) generate your OSM‐based city around that center
|
||
city_data = generate_osm_city_data(avg_lat, avg_lon)
|
||
|
||
# 5) sanitize building heights (replace NaN or missing with default 10.0)
|
||
default_height = 10.0
|
||
for b in city_data.get('buildings', []):
|
||
h = b.get('height')
|
||
if not isinstance(h, (int, float)) or (isinstance(h, float) and math.isnan(h)):
|
||
b['height'] = default_height
|
||
|
||
# 6) project all coords (and steps) to Web Mercator, recenter them
|
||
transformer = Transformer.from_crs("EPSG:4326", "EPSG:3857", always_xy=True)
|
||
# route
|
||
merc = [transformer.transform(lon, lat) for lon, lat in coords]
|
||
if not merc:
|
||
merc = [transformer.transform(*s['position']) for s in selected.get('steps', [])]
|
||
avg_x = sum(x for x, _ in merc) / len(merc)
|
||
avg_z = sum(z for _, z in merc) / len(merc)
|
||
rel_coords = [[x - avg_x, z - avg_z] for x, z in merc]
|
||
|
||
# steps
|
||
step_positions = []
|
||
for step in selected.get('steps', []):
|
||
lon, lat = step['position']
|
||
x, z = transformer.transform(lon, lat)
|
||
step_positions.append({
|
||
'x': x - avg_x,
|
||
'z': z - avg_z,
|
||
'step': step
|
||
})
|
||
|
||
# 7) render
|
||
return render(request, 'pxy_city_digital_twins/augmented_waste_route.html', {
|
||
'subdivision': subdivision,
|
||
'vehicle': vehicle,
|
||
'selected_route_json': json.dumps(selected),
|
||
'city_data': city_data,
|
||
'rel_coords': rel_coords,
|
||
'step_positions': step_positions,
|
||
})
|