Read ME
This commit is contained in:
parent
e4ca27541e
commit
ec7de54fe9
75
README.md
Normal file
75
README.md
Normal file
@ -0,0 +1,75 @@
|
||||
# Polisplexity Digital Twin Viewer
|
||||
|
||||
This application is a Django-based 3D digital twin city renderer using A-Frame and real-world OpenStreetMap (OSM) data. It allows visualization of buildings, fiber paths, cell towers, and other urban infrastructure in a simulated, interactive WebVR environment.
|
||||
|
||||
## ✨ Features
|
||||
|
||||
- 🔲 **Building extrusion from OSM**: Downloads building footprints with geometry and height/levels metadata and extrudes them into 3D blocks.
|
||||
- 🛰️ **Street network rendering**: Downloads local driving network and represents it visually as 3D fiber links.
|
||||
- 🏙️ **Recentered city layout**: All elements are normalized to a `(0,0)` coordinate center and scaled down to allow a bird’s-eye view or giant-perspective simulation.
|
||||
- 📡 **A-Frame-based environment**: Uses `aframe-environment-component` for sky, lighting, ground, and interactions.
|
||||
- 🎯 **Status gauges**: Each building displays a status gauge with a rotating ring and transparent glass core, labeled with mock status data.
|
||||
- 🧠 **Per-entity click interaction**: Clicking on a gauge changes its color and toggles the status (mocked).
|
||||
- 🌐 **Dynamic generation by coordinates**: Any city view can be created dynamically via URL parameters like `lat`, `long`, and `scale`.
|
||||
|
||||
## 🏗️ Stack
|
||||
|
||||
| Component | Technology |
|
||||
|--------------------|-----------------------------|
|
||||
| Backend | Django 5.x |
|
||||
| Mapping API | `osmnx`, `shapely`, `geopandas` |
|
||||
| Frontend (3D) | A-Frame 1.7.0 |
|
||||
| Visualization Libs | `aframe-environment-component` |
|
||||
| Deployment Ready? | Yes, via Docker + Gunicorn |
|
||||
|
||||
## 🔌 Example Usage
|
||||
|
||||
To load a city block from Centro Histórico, Mexico City:
|
||||
|
||||
```
|
||||
|
||||
[http://localhost:8001/city/digital/twin/osm\_city/?lat=19.391097\&long=-99.157815\&scale=0.1](http://localhost:8001/city/digital/twin/osm_city/?lat=19.391097&long=-99.157815&scale=0.1)
|
||||
|
||||
````
|
||||
|
||||
## 🧪 Directory Highlights
|
||||
|
||||
- `pxy_city_digital_twins/views.py`: Request handler that decides which generator to use (`osm_city`, `random_city`, etc.)
|
||||
- `services/osm_city.py`: Main generator for real-world urban geometry based on lat/lon.
|
||||
- `templates/pxy_city_digital_twins/city_digital_twin.html`: A-Frame scene renderer.
|
||||
- `templates/pxy_city_digital_twins/_status_gauge.html`: UI fragment for interactive gauges on city elements.
|
||||
|
||||
## 📦 Dependencies
|
||||
|
||||
Add these to `requirements.txt`:
|
||||
|
||||
```txt
|
||||
osmnx>=1.9.3
|
||||
shapely
|
||||
geopandas
|
||||
````
|
||||
|
||||
Optional (for better performance in prod):
|
||||
|
||||
```txt
|
||||
gunicorn
|
||||
dj-database-url
|
||||
```
|
||||
|
||||
## 🚧 To-Do
|
||||
|
||||
* [ ] Load `status` from a real database or agent simulation
|
||||
* [ ] Add 3D models (e.g., trees, street furniture)
|
||||
* [ ] Support texture-mapped facades
|
||||
* [ ] Add time-based simulation / animation
|
||||
* [ ] Integrate sensor/IoT mock data stream
|
||||
|
||||
## 👀 Screenshot
|
||||
|
||||
> *Coming soon* — consider generating A-Frame scene screenshots automatically using headless browser tools.
|
||||
|
||||
---
|
||||
|
||||
**Maintained by [Hadox Research Labs](https://hadox.org)**
|
||||
|
||||
|
Binary file not shown.
@ -2,37 +2,48 @@ import osmnx as ox
|
||||
import shapely
|
||||
import random
|
||||
import uuid
|
||||
import networkx as nx
|
||||
from matplotlib import cm
|
||||
|
||||
def generate_osm_city_data(lat, lon, dist=400, scale=1.0):
|
||||
print(f"🏙️ Fetching OSM buildings at ({lat}, {lon})")
|
||||
print(f"🏙️ Fetching OSM buildings and network at ({lat}, {lon})")
|
||||
|
||||
scale_factor = scale # Shrinks the city to make the camera look like a giant
|
||||
scale_factor = scale
|
||||
status_options = ["OK", "Warning", "Critical", "Offline"]
|
||||
|
||||
# ————— 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")
|
||||
|
||||
# ————— 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 = gdf[gdf.geometry.type.isin(["Polygon", "MultiPolygon"])].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"]
|
||||
building_id = f"BLD-{uuid.uuid4().hex[:6].upper()}"
|
||||
status = random.choice(status_options)
|
||||
|
||||
try:
|
||||
height = float(row.get("height", None))
|
||||
except:
|
||||
try:
|
||||
height = float(row.get("building:levels", 3)) * 3.2
|
||||
except:
|
||||
height = 10.0
|
||||
height = float(row.get("building:levels", 3)) * 3.2 if row.get("building:levels") else 10.0
|
||||
|
||||
building_id = f"BLD-{uuid.uuid4().hex[:6].upper()}"
|
||||
status = random.choice(status_options)
|
||||
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,
|
||||
@ -41,7 +52,7 @@ def generate_osm_city_data(lat, lon, dist=400, scale=1.0):
|
||||
"width": polygon.bounds[2] - polygon.bounds[0],
|
||||
"depth": polygon.bounds[3] - polygon.bounds[1],
|
||||
"height": height,
|
||||
"color": "#8a2be2",
|
||||
"color": hex_color,
|
||||
"status": status,
|
||||
})
|
||||
|
||||
@ -50,9 +61,7 @@ def generate_osm_city_data(lat, lon, dist=400, scale=1.0):
|
||||
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({
|
||||
buildings = [{
|
||||
"id": b['id'],
|
||||
"position_x": (b['raw_x'] - avg_x) * scale_factor,
|
||||
"position_z": (b['raw_z'] - avg_z) * scale_factor,
|
||||
@ -61,10 +70,8 @@ def generate_osm_city_data(lat, lon, dist=400, scale=1.0):
|
||||
"height": b['height'] * scale_factor,
|
||||
"color": b['color'],
|
||||
"status": b['status'],
|
||||
})
|
||||
} for b in raw_buildings]
|
||||
else:
|
||||
buildings = []
|
||||
|
||||
return {
|
||||
"buildings": buildings,
|
||||
}
|
||||
return {"buildings": buildings}
|
||||
|
Loading…
x
Reference in New Issue
Block a user