I sometimes fall into the trap of thinking writing and the hobbies I write about as a one-way street: I do something interesting, and then I write about it. Rarely do I consider the other way around: I write something that inspires me in real life. This is partly structural: rarely do I write when I don’t have a subject to write about (much to the chagrin of the search engine optimization bots I find lurking in my site’s spam folder, hawking “algorithmic content generation services”... yeesh, no thanks). It’s also partly driven by my personal preferences: for me, programming is a series of fun logic puzzles, each of which when solved results in a little endorphin rush and feeling of pride and accomplishment. Writing, on the other hand, offers me all the fun of typing and retyping the same sentence over and over again (The early 90’s edutainment game Mario Teaches Typing is a fairly accurate representation of my writing process) until I’m “happy” with it, and then moving on to the next sentence to repeat the process. But writing has its benefits--in this case, all that introspection ended up inspiring me to develop a new feature now available in the latest version of rayshader.

In my last post I introduced the new 3D plotting features of rayshader: building beautiful 3D raytraced maps directly from an elevation matrix. It was more than just making the maps 3D, however–I wanted to make them look like a solid object. To accomplish this, I added in a base and a shadow:

Figure 1: Transforming a map from a thin surface into a solid object with the rayshader package in R. The map is Hobart, Tasmania (if Hobart, Tasmania was re-imagined as a moonscape with a river of unicorn tears).

When I described them in the blog post, I wrote the following:

I didn’t want the map to look like a carefully crinkled sheet of paper; I wanted a 3D representation that looked like a paper weight, one that you could imagine setting on your work desk and occasionally picking up and examining when you needed a mid-day moment of introspection (i.e. when you are fidgeting).

Writing that got me thinking: how cool would it be actually have one of these maps for real? I wrote that bit about introspection and realized–actually, yes please, I would indeed like a fidget map, thank you (me) very much. The first question was now: how the hell do I do that? Also, how do I convert my 3D on-screen maps to a 3D printable format? Also–how do 3D printers work, and what exactly is that 3D printable format I need?

I feel ya, buddy.

A few google searches later, and I had a rough draft of my answers: The 3D printable format I was looking for was a stereolithography (STL) file, which thankfully had an export function already in the rgl package. However, I quickly found that the output of this function applied to rayshader was not in a “production 3D printer-ready” format: The default orientation was 90 degrees off 1, and the size was determined by the size of the underlying matrix–an 800x800 matrix would be bigger and more expensive to print than a 400x400 matrix–and wasn’t able to be specified by the user via that function. And if the user turned on the water layer, shadow layer, or any other aesthetic option in plot_3d it would include those in the print as well.

I originally fixed the issues with the free "meshlab" software, but it wasn’t exactly a solution that I would wish upon end users. It's the main problem I've found when seeing other people explain how to 3D print topographic maps online: the toolchain they describe usually involved software that was way overkill for the intended usage: "Step 1) Download, install, and learn to use QGIS/Blender. Step 2) Now... wait, where did you go?" So, I wrote my own function and included it in rayshader, save_3dprint() (here montereybay and volcano are just N*M matrices of elevation values):

library(rayshader) 
#Printing the included `montereybay` dataset:
montereybay %>%
  sphere_shade() %>%
  plot_3d(montereybay,zscale=50)
save_3dprint("montereybay_3d.stl", maxwidth = 150, unit = "mm")
## Dimensions of model are: 150.0 mm x 150.0 mm x 38.2 mm
#Printing `volcano`:
volcano %>%
  sphere_shade() %>%
  plot_3d(volcano,zscale=3)
save_3dprint("volcano_3d.stl", maxwidth = 4, unit = "in")
## Dimensions of model are: 4.00 inches x 2.79 inches x 1.88 inches

save_3dprint() takes the on-screen 3D matrix, strips off all the aesthetic options (water, lines, shadow), turns it into an STL file, applies transformations to the polygons to correct the orientation and scale the matrix to the size the user wants, and then writes out the print-ready STL file. Notice that it only takes four lines of code to go from source data to finalized 3D printable file--dead simple. It also displays the dimensions of the print when you’re done, so you can sanity check the size of the model before finalizing it. Then it's ready to be sent off to the printers.

Figure 2: Pulled out the DSLR for this shot. Hobart, Tasmania printed in Polylactic Acid (PLA).

A quick trip to an online 3D printing service, $25, and two weeks later: I now have a nice little representation of Hobart, Tasmania sitting on my desk. I'm excited about the other possibilities easy 3D printing brings to the table: combined with the upcoming 3D ggplot2 feature, you can also print out 3D plots to represent your data. Other than the obvious novelty of having a 3D ggplot, think of the accessibility potential this adds to R: you can now create visualizations (tactilizations? feelizations?) accessible to the visually impaired/blind. You can 3D print a version of your plots if you know someone who might need it will be interested in your talk/data.

Figure 4: The diamond dataset in R, visualized in a hexbin plot, transforming between a regular 3D ggplot and a braille 3D ggplot.

And gift ideas abound: if someone you love2 has a favorite dataset or map, you can easily print it out and bring that enjoyment out of the computer screen and into their life. Create customized gifts for the GIS-inclined. Add buildings to your elevation matrix and 3D print a cityscape. Give some flavor to your D&D game by generating, printing, and painting a custom world map. Or just buy a rotating stand and watch Hobart, Tasmania spin around in circles, a normal person activity that normal people definitely do.

And most impressively: It’s the first thing I’ve ever created in R that has caught the interest of our cat. Catographer approved.

Figure 4: Oops, I guess I accidentally imported forcats.

And if you want to see more cool stuff, follow me on Twitter and sign up for my newsletter for the latest updates on rayshader! For any easy source of topographic data, check out this website.

X
Love maps and data? Subscribe to my newsletter to learn more!

You can download the latest version with 3D printing support from GitHub: rayshader on github

About the Author:

Tyler is a data scientist, physicist, writer, and programmer. Follow @tylermorganwall

12 Comments

  1. Danal Estes August 28, 2018 at 4:41 pm - Reply

    Very nice write-up. I also have an blog to which I post only very occasionally, mainly so I can link to a permanent spot from more volatile media sites… and I too get creeped out by all the SEO promotion.

    Anyway, that paragraph resonated with me

  2. saso_008 June 29, 2019 at 3:14 pm - Reply

    hi, i get topographic data from http://dwtkns.com/srtm30m/ website but data is hgt and how can use this data for create 3D map?

  3. Neil November 10, 2020 at 10:52 pm - Reply

    Is it possible to produce an stl file that contain the colours of the 3d model?

    • Tyler Morgan-Wall November 16, 2020 at 4:22 pm - Reply

      Hi Neil, colors aren’t possible for 3D prints—the STL format doesn’t support them.

  4. Benoit January 4, 2021 at 1:34 pm - Reply

    Hi Tyler,
    Thanks again for these amazing packages and the shortcut to 3D print rayshader scenes .
    I was wondering if there was a similar approach to save a scene created with rayrender as an .stl file and then print it with a 3D printer.

    • Tyler Morgan-Wall January 4, 2021 at 4:26 pm - Reply

      Thanks! No, not currently. Rayrender visualizations can be extremely complex (versus rayshader 3D terrains), so exporting them to a STL file would be much more complex.

  5. Mike Johnson January 6, 2021 at 6:39 am - Reply

    Hello,

    I love this package! I’ve been trying to export a small map area to an STL and am having a really hard time getting the file size down. I’m starting with two adjacent .hgt files that I’m cropping down by Lat/Lon to what winds up being a 1620×1620 matrix. When I plot_3d() and save_3dprint() on this matrix, I wind up getting an STL file that’s over 500MB. I read on another forum where you responded to a question about this by suggesting adjusting the height and width in the plot_3d() call, but width and height aren’t available variables there so far as I can tell. I’ve tried messing with the windowsize variable, I know that the dimensions in the 3dprint() call don’t affect size. I also tried using triangulate but it didn’t drop my file size. One thing I was sue would work was aggregating my matrix by a factor of 4 to reduce the number of vertices by a factor of 16, but I still got a huge file. They’re too big to even load into Meshmixer to reduce effectively. Any suggestions? I’d love to try printing this out!

    Thank you!

    • Tyler Morgan-Wall January 6, 2021 at 3:59 pm - Reply

      Hi Mike,

      Try passing the matrix to the resize_matrix() function. A 1620×1620 matrix means your STL file has 5,248,800 triangles for just the surface, which is leading to the large file sizes and is probably overkill for the level-of-detail you’d require (unless you’re 3D printing a large object). Set resize_matrix(mat, 0.25) or resize_matrix(mat, 0.5). 0.5 should cut the file size to 1/4th (~125MB) and 0.25 should cut it to about 30MB.

      • Mike Johnson January 7, 2021 at 3:03 am - Reply

        Perfect, that worked. Thank you!

  6. Evan Barnes April 20, 2021 at 11:45 pm - Reply

    Hi Tyler! I’m new to R and I’m very excited about this package, but I could use a bit of assistance.

    Using guidance from this page and from the rayshader website, I’m able to extract an .stl file from a GeoTIFF. However, when printed I would prefer for it to have a hexagonal base. This is where I run into my issue:

    According to guidance on the rayshader website, changing the base type can be done by setting the plot_3d parameter baseshape=’hex’. However, when I do this, my .stl becomes un-filled. Whereas the square based .stl has a bottom and is enclosed, the hex-based version resembles a tent with only the terrain surface and sides present. This makes it unsuitable for my slicer to process. The baseshape parameter is the only input that I am modifying between the two versions. I have included the relevant code below, minus the aesthetic options.

    elmat = rater_to_matrix(my_geotiff)

    elmat %>%
    sphere_shade(texture = “imhof1”) %>%
    plot_3d(elmat, zscale = 2.5, fov = 0, theta = 135, zoom = 0.75, phi = 45,
    windowsize = c(1000, 800), baseshape = “hex”)

    What should I modify to ensure that save_3dprint() will produce an .stl file with a base?

    Thank you for your time,
    -Evan

  7. siyengar October 17, 2021 at 5:00 am - Reply

    great tutorial! for some reason, with a custom dataset, I get the following error after calling the save_3dprint method:
    Error in if (id) rgl.attrib.count(id, “colors”) else rgl.getcolorcount() :
    argument is not interpretable as logical
    not sure what it means, and my matrix is 3601×3601, scaled down by 0.125 to 450×450. can you help me with this error?

    • Florian December 8, 2021 at 10:29 pm - Reply

      I got the same error in trying to reproduce the volcano and Monterey Bay examples. However, the error only occurs if I close the XQuartz window/RGL device *before* running the save_3dprint function, e.g. the XQuartz window needs to be open for save_3dprint to work. Makes sense on second thought, but is not completely intuitive.

Leave A Comment