Minecraft Mapping with Lidar Data

Minecraft Mapping with Lidar Data

A few weeks ago, I had an idea. I was going to use lidar datasets to create 3D interactive maps of parks and trails around my town, and they were going to be beautiful. But like many dreams, mine was never achieved (mostly because I gave up on learning Three.js).

Then, I had a better dream. The heightmaps I was creating from lidar data reminded me of the top-down view of terrain in Minecraft - a popular "video-game," in the parlance of the digitally savvy. I set to work finding software that would do most of my work for me, and found WorldPainter. WorldPainter is an easy to use Minecraft mapping tool that supports heightmap imports. That's pretty much the whole job done!

So, here's my workflow to get data, convert it, and use it in WorldPainter. To follow these instructions, you'll need a computer that can run the R programming language, and of course the WorldPainter application.

Step 1: Find and download the lidar data

A photo of the USGS data tool

The USGS has made lidar data covering much of the continental US available for download. Their downloader tool is easy to use: just select a rectangular area you want data from, and choose "Elevation source data" > "Lidar Point Cloud." I'll go into what a lidar point cloud is later, but for now just know that these files are the raw data we'll make our heightmap from.

Once you've selected your data, click "Search Products" and you'll be greeted with a list of datasets that cover your search area. Select "Show All Footprints" and the areas these datasets cover will be displayed.

A photo of the USGS 3DEP data tool

If your search area is small enough, you can just download the .laz files individually. If it's larger, you may want to use their bulk download program. Once you've got your files, move on to step 2!

Step 2: Convert the Data

Now we'll be putting the data into a more useful format. First, a caveat: I'm no GIS professional (although if you're reading this and want to change that, I'm looking for work). My understanding of lidar data and other GIS data formats may not be perfect!

A lidar point cloud, showing an area of lake rabun, GA

Lidar, a portmanteau of "light" and "radar," is a technology that measures distances by shooting lasers. A lidar point cloud is a collection of three dimensional points. It can be useful to think of these points as vertices on a 3D object, which represents the actual object the lidar is . But since these points are irregularly dispersed, we'll have to put them into a gridded format that can be easily converted into Minecraft blocks.

That format is referred to as a 'raster.' You may know this word in relation to computer graphics; a raster image is one which stores its color data in gridded cells. Just like a raster image, we'll be storing our data in gridded cells, although the information we'll be putting in those cells won't describe the color, but rather the height of the cell.

To do this, we'll be using some very convenient functions provided by the 'lidR' library, an R package for lidar data imaging and manipulation. If you don't know anything about coding with R, don't worry! We'll be doing all of this with only 8 lines of code. If you need to install R, I recommend following this simple guide to set up and use R with RStudio.

Open up a new script, and set the working directory to your source file location (Session > Set Working Directory > To Source File Location). Then, put your downloaded .laz files in a folder in that same file location. Now all you have to do is run this script (replacing the placeholder folder name on line 5 with whatever folder holds your files):

#these lines install and load the necessary packages
install.packages(c('lidR', 'raster'))
library(lidR)
library(raster)

#this will allow lidR to use as many threads as possible when computing later
lidR::set_lidr_threads(0)

#this line will find all the .laz files in your folder so they can be read
lidar_files<- list.files('lidar/rabun', full.names = TRUE)

#this reads and stores the lidar data! fun!!
las<- readLAS(lidar_files)

#this converts the point cloud to a raster layer
dtm<- grid_terrain(lasfilterground(las), res=1, knnidw(k = 5, p = 2))

#this writes your raster to a .tif file
writeRaster(dtm, "dtm_ground.tif", type="INT2U")


Let's go through what we did in that code. First we installed and loaded the libraries we needed (easy). Then we gave lidR permission to use all our CPU power (fun). Then we loaded in all our .laz files into one lidar data object. The lidR package will automatically stitch them together! Finally we come to the very CPU intensive part of the code: converting our point clouds to a raster.

To do this, we're using an algorithm that averages the height of points within a one meter cell. The heights of the points are inverse weighted based on their distance from the center. This algorithm, abbreviated in our code as 'knnidw,' is the nearest neighbor inverse distance weighting algorithm. In this same line, we're filtering our point cloud to only use points representing the ground.

While we're here, you'll need to get some data for use with WorldPainter later. When we import our height map, we'll need to know the minimum and maximum values of our data. To access these values, simple use the following commands in the R console:

#minimum height value
dtm@data@min

#maximum height value
dtm@data@max

Keep track of these values for reference later!

No alt text provided for this image

The resulting raster is what I'll be referring to as our heightmap. The image you see above is a png rendering of the heightmap; each pixel corresponds to an square geographic area, and the color depicts the height. However, when using certain datasets, we run into a problem: the height range is greater than 256 meters. This means that when we convert it to a minecraft world, we'll have to scale down our heights. It also means that we can't use a png like the one above for storing our data, because 8-bit png color values range from 0-255 (or, 2⁸). So instead we'll write our data to a .tif file, which is frequently used for storing geographic data. Specifically, we'll be using the INT2U data type, which allows for values from 0-65,534. Next we'll import this file into WorldPainter!

Step 3: Import into WorldPainter

In case you still need to download WorldPainter, you can do so at this link. It's a very fun and easy to use program, and I recommend you play around with it on your own for a while! We'll be doing most of our work with a single features.

Once you've loaded up, just navigate to File>Import new world>From height map, or use the Ctrl+M shortcut. You'll be greeted with the height map import interface.

No alt text provided for this image

Select your height map and import it. As you can see from the photograph above, you'll need the minimum and maximum height values we found before to tell the program where to start scaling the data from our height map into Minecraft levels. My dataset starts at 514 meters, and tops out at 752 meters.

The last bit of adjusting we'll need to do is fairly simple: we need to tell Minecraft where the water level of our height map is. In my data, the water is the lowest point; to keep the water from sitting right at bedrock, I'm going to make my bottom height correspond to 30 blocks about bedrock, and set my water level to a few blocks above that. Next, just click OK and your Minecraft map will be imported!

No alt text provided for this image

From here we can make minor edits to our map using the tools provided. When you're ready to export your world, just go to File>Export>Export as new Minecraft map, or Ctrl+E. To make your world playable, you'll probably want to populate it with trees and minerals using the various export settings. Play around with it until you find something right!

Here's a gallery of some screenshots of the world I made using 20+ .laz files covering the entirety of Lake Rabun. I rendered these with the Optifine mod, and the SEUS shader pack.

No alt text provided for this image
No alt text provided for this image
No alt text provided for this image
No alt text provided for this image



Great info, and enough to get me started. A couple of things has changed in R, so here are my tweaks to get it working with my datasets: ##Couldnt find the right menu to change the working directory, but it can be done in the console. setwd('c:/tmp') ## Confirm that it's correct: getwd() ##Load the libraries(install if needed with"install.packages(c('lidR', 'raster'))" library(lidR) library(raster) ## Set threads to something reasonable, so you leave a little reasources to other things. lidR::set_lidr_threads(8) ## filter out just the files needed laz_files<-list.files('c:/tmp',pattern='*.laz') ## Print out the file list to confirm. print (laz_files) ## Read the data into dtm (lasfilterground is now named filter_ground) dtm<-grid_terrain(filter_ground(las), res=1,knnidw(k=5,p=2)) ## write the tiff file (type now is named "datatype" writeRaster(dtm,'dtm.tif', overwrite=TRUE, datatype='INT2U') ## Find max and min levels in the dataset dtm@data@max dtm@data@min 😀

To view or add a comment, sign in

More articles by Justin Ebert

  • Implementing and Evaluating Gen-AI Coding Tools

    For sixty years computer programmers have considered our labor as something beyond what other people do. We're…

  • Simulating 3D Motion With Sine Waves

    Check out this post on my blog, where all the animations are generated dynamically and there's limited interactivity I…

  • Baseball by the Numbers

    Last fall, I dove headfirst into the world of baseball data. I started with a simple question: how often do the Braves…

    1 Comment

Others also viewed

Explore content categories