Chris Parmer — home

Synthetic LiDAR point cloud

Remote Sensing — building + ground + trees

Example from the compendium of canonical charts

Remote Sensing — Synthetic LiDAR point cloud (building + ground + trees)

Python Code

"""Remote Sensing — Synthetic LiDAR point cloud (building + ground + trees)."""


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)),
        ),
    )

    return fig


if __name__ == "__main__":
    generate()

Made with Plotly