GeoJSON to STL: terrain 3D printing

We reconstructed the STL mesh from the GeoJSON data by extracting the GeoJSON shape coordinates into the point cloud and applying Poisson reconstruction using Open3d.
Insert image description here

Recommended: Use NSDT editor to quickly build programmable 3D scenes

I was deeply dissatisfied with my first attempt at printing GeoJSON hills, so I came up with a three-step process to generate high-fidelity replicas using only open source software. This process is divided into three steps:

  • Slicing large geojson maps using Geopandas.
  • Create XYZ point clouds of geojson geometry (using Shapely and Geopandas).
  • Use Open3D to build a TriangleMesh for an XYZ point cloud.
  • Use Blender to extrude solid bodies from this mesh.

For this iteration, we'll try to model the cliffside, including Telegraph Hill. If you want to try it yourself, here is my script and SF GeoJSON data.

But I found a better way to print 3D terrain: you can use the online tool NSDT 3DConvert to convert GeoJSON to STL. You don’t need to install any software locally. You only need to drag and drop your GeoJSON file into the panel of the web page. Enough:

Insert image description here

1. Slice of the Earth

GeoPandas extends the Pandas data frame with geographic data and spatial primitives. It allows us to clip shapes to a specified window of longitude, latitude we are interested in, convert coordinates to UTM X and Y meters, and more.

First, we slice the SF geojson map so that it only contains features (and aspects of the features) within Telegraph Hill coordinates:

#!/usr/bin/env python3
import geopandas
from shapely.geometry import Polygon

outname = "telegraph_hill"
y1, x1 = (37.8053307084282, -122.40853235179131)
y2, x2 = (37.79953896610929, -122.40128101638189)

gdf = geopandas.read_file('geojson/sf.geojson')
polygon = Polygon([(x1, y1), (x1, y2), (x2, y2), (x2, y1), (x1, y1)])
gdf_clipped = gdf.clip(polygon)
gdf_clipped.to_file(f"geojson/{outname}.geojson", driver='GeoJSON')
gdf_clipped = gdf_clipped.explode(index_parts=True)  #Line Segments only!

2. Generate point cloud from GeoJSON

Step 1 gives us a "GeoDataFrame" of the shape within the desired coordinates. We now need to convert these related shapes into a point cloud and then into a triangle mesh for 3D printing.

We will continue to use geopandas to convert the coordinates from long, latitude to X, Y meters from the origin and join these coordinates with the Z data in the "elevation" attribute of the source data. These data points then initialize the Open3d point cloud.

import open3d as open3d

CONV_FT_TO_M = 0.3048  # SFData provides elevation in feet :(

def compute_gdf_pointcloud(gdf: geopandas.GeoDataFrame, z_scale: float) -> o3d.geometry.PointCloud:
    """Compute the PointCloud of the GeoDataFrame."""
    min_x, min_y = (99999999,99999999)
    min_elevation = min([e for e in gdf['elevation']])
    
    for f in gdf['geometry']:
        for c in f.coords:
            if c[0] < min_x:
                min_x = c[0]
            if c[1] < min_y:
                min_y = c[1]

    logging.debug(f"min_x={min_x} min_y={min_y} min_elevation={min_elevation}")
    gdf['flat_coordinates'] = gdf['geometry'].combine(
        gdf['elevation'],
        (lambda g, e: [(float(c[0] - min_x),
                        float(c[1] - min_y),
                        float(e) * CONV_FT_TO_M * z_scale) for c in g.coords]))
    # Add these coordinates to an Open3d point cloud.
    pcd = o3d.geometry.PointCloud()
    pcd.points = o3d.utility.Vector3dVector()
    for coordinate_list in gdf['flat_coordinates']:
        pcd.points.extend(o3d.utility.Vector3dVector(coordinate_list))
    # Estimate normals.
    pcd.estimate_normals(search_param=o3d.geometry.KDTreeSearchParamHybrid(radius=0.01, max_nn=30))
    pcd.orient_normals_to_align_with_direction()
    return pcd

Insert image description here

3. Construct TriangleMesh for XYZ point cloud

We now try to reconstruct a Poisson mesh from a point cloud using normals. Poisson grid reconstruction also returns density data for each inferred triangle, which allows us to prune irrelevant inferred regions if necessary. This is also our only interesting color.

import open3d as o3d
import numpy as np
import matplotlib.pyplot as plt

def compute_poisson_mesh(pcd: o3d.geometry.PointCloud, depth: int) 
   -> o3d.geometry.TriangleMesh:
    """Compute the mesh of the point cloud.
    depth:    The depth of the octree used for the surface reconstruction determines resolution of the resulting triangle mesh.
    """
    poisson_mesh, densities = o3d.geometry.TriangleMesh.create_from_point_cloud_poisson(
        pcd, depth=depth, width=0, scale=1.1, linear_fit=True)
    # Color the supporting point density.
    densities = np.asarray(densities)
    graph_rate = (densities - densities.min()) / (densities.max() - densities.min())
    density_colors = plt.get_cmap('plasma')(graph_rate)
    density_colors = density_colors[:, :3]
    poisson_mesh.vertex_colors = o3d.utility.Vector3dVector(density_colors)
    # Trim any excess surface from the Poisson reconstruction.
    bbox = pcd.get_axis_aligned_bounding_box()
    p_mesh_crop = poisson_mesh.crop(bbox)
    p_mesh_crop.compute_triangle_normals()
    return p_mesh_crop

Insert image description here

Overall not bad! Extrapolating from the point density, the color of the grid indicates how many points (from our point cloud) represent the confidence of a given polygon. Since our process benefits from a rectangular grid (see next step), we won't trim any excess areas.

4. Repair and 3D printing

We need to fix some holes in the geometry and optionally create a valid solid (the mountain should be hollow). To fix the hole, import the STL into Blender and select the vertices around the hole in edit mode, then press "alt-F" to fill the hole with a triangle.

Insert image description here

General solutions for STL vulnerability patching are left as an exercise to the reader.

We should now be able to extrude the mesh and keep the upper part using the Bisect tool. Check for non-manifold surfaces along the way and recalculate normals before exporting.
Insert image description here

This is much better than I originally expected!


Original link: GeoJSON to STL printing terrain—BimAnt

Guess you like

Origin blog.csdn.net/shebao3333/article/details/132916929