Synthetic LiDAR point cloud
Remote Sensing — building + ground + trees
Example from the compendium of canonical charts
Python Code
"""Remote Sensing — Synthetic LiDAR point cloud (building + ground + trees)."""
from pathlib import Path
# ── Palette + theme (matching the Plotly Studio gallery these charts ship in) ──
VIOLET, TEAL, GREEN, PINK, ORANGE = "#845EEE", "#52B3D0", "#55B685", "#DA5597", "#E9A23B"
PRIMARY, SECONDARY = VIOLET, TEAL
COLORWAY = [VIOLET, TEAL, GREEN, PINK, ORANGE]
BG, TEXT, GRID, MUTED = "#ffffff", "#1c2024", "#d9d9e0", "#60646c"
FONT = "Inter, -apple-system, BlinkMacSystemFont, sans-serif"
COLORSCALE = [[0, "rgba(132, 94, 238, 0.05)"], [1, "rgba(132, 94, 238, 0.9)"]]
def apply_theme(fig):
"""Light gallery theme: white background, Inter font, soft gridlines."""
fig.update_layout(
paper_bgcolor=BG, plot_bgcolor=BG, colorway=COLORWAY,
font=dict(family=FONT, color=TEXT, size=12),
legend=dict(font=dict(color=TEXT)),
hoverlabel=dict(bgcolor="#f0f0f3", font=dict(color=TEXT, family=FONT), bordercolor=GRID),
)
fig.update_xaxes(gridcolor=GRID, linecolor=GRID, zerolinecolor=GRID)
fig.update_yaxes(gridcolor=GRID, linecolor=GRID, zerolinecolor=GRID)
def fetch_csv(url, **kwargs):
import io
import pandas as pd
import requests
r = requests.get(url, timeout=60)
r.raise_for_status()
return pd.read_csv(io.StringIO(r.text), **kwargs)
def fetch_json(url):
import requests
r = requests.get(url, timeout=60)
r.raise_for_status()
return r.json()
import numpy as np
import plotly.graph_objects as go
def generate():
RNG = np.random.default_rng(7)
pts_x, pts_y, pts_z, pts_cls = [], [], [], []
# ── Ground plane (class 2) ────────────────────────────────────────────────────
n_ground = 5000
gx = RNG.uniform(0, 100, n_ground)
gy = RNG.uniform(0, 100, n_ground)
gz = RNG.normal(0, 0.05, n_ground)
pts_x.append(gx); pts_y.append(gy); pts_z.append(gz)
pts_cls.append(np.full(n_ground, 2))
# ── Building (class 6) — rectangular prism 40×40×15 centered at (50,50) ──────
# Walls
n_wall = 1500
for x0, x1, y0, y1 in [(30, 70, 30, 30), (30, 70, 70, 70),
(30, 30, 30, 70), (70, 70, 30, 70)]:
n = n_wall // 4
wx = RNG.uniform(x0, x1, n) if x0 != x1 else np.full(n, x0)
wy = RNG.uniform(y0, y1, n) if y0 != y1 else np.full(n, y0)
wz = RNG.uniform(0, 15, n)
pts_x.append(wx); pts_y.append(wy); pts_z.append(wz)
pts_cls.append(np.full(n, 6))
# Roof
n_roof = 800
rx = RNG.uniform(30, 70, n_roof)
ry = RNG.uniform(30, 70, n_roof)
rz = np.full(n_roof, 15.0) + RNG.normal(0, 0.05, n_roof)
pts_x.append(rx); pts_y.append(ry); pts_z.append(rz)
pts_cls.append(np.full(n_roof, 6))
# ── Trees (class 5) — cones at random positions outside building ──────────────
tree_positions = [(15, 15), (85, 20), (10, 80), (80, 75), (20, 55)]
for tx, ty in tree_positions:
n_tree = 300
h_max = RNG.uniform(6, 12)
tz = RNG.uniform(0, h_max, n_tree)
radius = (h_max - tz) / h_max * 3.0 + RNG.normal(0, 0.2, n_tree)
theta = RNG.uniform(0, 2 * np.pi, n_tree)
tree_x = tx + radius * np.cos(theta)
tree_y = ty + radius * np.sin(theta)
pts_x.append(tree_x); pts_y.append(tree_y); pts_z.append(tz)
pts_cls.append(np.full(n_tree, 5))
X = np.concatenate(pts_x)
Y = np.concatenate(pts_y)
Z = np.concatenate(pts_z)
print(f"Total points: {len(X)}")
fig = go.Figure(go.Scatter3d(
x=X,
y=Y,
z=Z,
mode="markers",
marker=dict(
size=2,
color=Z,
colorscale="earth",
colorbar=dict(title="Elevation (m)", thickness=12),
opacity=0.8,
),
hovertemplate="X: %{x:.1f}m<br>Y: %{y:.1f}m<br>Z: %{z:.1f}m<extra></extra>",
))
fig.update_layout(
scene=dict(
xaxis_title="Easting (m)",
yaxis_title="Northing (m)",
zaxis_title="Elevation (m)",
aspectmode="manual",
aspectratio=dict(x=1.5, y=1.5, z=0.3),
camera=dict(eye=dict(x=1.5, y=-1.8, z=1.2)),
),
)
apply_theme(fig)
return fig
fig = generate()
fig.show()
Made with Plotly