Skip to main content

GeoJSON to 3D Exploration - Tests and Implementations

First off, for the tests, I will be trimming our original GeoJSON down to a subset of ~10 or so connected sewer mains. I will perform this trimming with QGIS by manually selecting sections to trim off.

PyVista - Answer 1.1

pip install pyproj pyvista trimesh shapely

import pyvista as pv
import numpy as np
from pyproj import Proj, transform
from geojson import load
from shapely.geometry import LineString, MultiLineString

# Define your WGS84 projection (lat-long degrees)
proj_wgs84 = Proj(proj="latlong", datum="WGS84")
# Define a local ENU projection based on a reference origin (latitude, longitude, altitude)
origin_lat, origin_lon, origin_alt = 40.7128, -74.0060, 0 # Example: NYC
proj_enu = Proj(proj="utm", zone=33, datum="WGS84") # UTM zone varies by location

# Parameters for cylinder generation
radius = 0.5 # Pipe radius
resolution = 24 # Cylinder smoothness

# Load GeoJSON
with open("sewer_mains.geojson", "r") as f:
geojson_data = load(f)

# Function to convert lat-long to ENU (local Cartesian)
def latlon_to_enu(lat, lon, alt):
x, y = transform(proj_wgs84, proj_enu, lon, lat)
return x, y, alt

# Extract features and generate 3D pipes
pipes = []
for feature in geojson_data["features"]:
geom = feature["geometry"]
if geom["type"] == "LineString":
coords = geom["coordinates"]
path = LineString(coords)
elif geom["type"] == "MultiLineString":
coords = geom["coordinates"]
path = MultiLineString(coords)
else:
continue

# Generate a 3D pipe for the path
for segment in path.geoms if path.geom_type == "MultiLineString" else [path]:
points = [latlon_to_enu(lat, lon, 0) for lon, lat in segment.coords]
for i in range(len(points) - 1):
start, end = np.array(points[i]), np.array(points[i + 1])
vector = end - start
length = np.linalg.norm(vector)

# Create a cylinder
cylinder = pv.Cylinder(center=(start + end) / 2, direction=vector, radius=radius, height=length, resolution=resolution)
pipes.append(cylinder)

# Combine all pipes into one mesh
mesh = pv.MultiBlock(pipes).combine()

# Export to glTF
mesh.save("sewer_mains.gltf")