Stratospheric Solar Balloon Flight

It took three years and several false starts, but we finally got a solar balloon with a video camera on board to take off successfully.  Not only that, but the balloon reached an elevation of over 22,000 meters (72,000 feet), well into the stratosphere!  Then, it suffered what Elon Musk calls a “rapid unscheduled disassembly,” sending the payload into a 22 kilometre free fall that ended in the muddy banks of a cow pond.  Despite this, the photos, video, and GPS track were all recovered.  Here’s the flight video:

The balloon envelope consisted of a 19 foot diameter sphere of 0.31 mil clear plastic paint drop cloth.  Since the drop cloth comes in 12 x 400 foot sheets, we constructed the envelope from 5 gores that were 12 feet wide at the equator.  We attached the gores together using clear shipping tape and darkened the interior of the balloon with black paint pigment.  Each gore seam had a string attached to the bottom with black duct tape.  The opening at the bottom of the balloon is about 6 feet across, allowing two people to inflate it by hand (check out the full inflation and launch video).

The solar balloon in flight just after launch.  Image credit: Mary Lide Parker

The solar balloon in flight just after launch. Image credit: Mary Lide Parker

The payload consisted of a SPOT satellite tracker for recovery, an Arduino Uno with Adafruit High Altitude GPS Shield for trajectory determination, and a Raspberry Pi with camera module for video and stills.  The system was powered using a lithium battery pack meant for recharging cell phones (10 ampere hours).  All this was contained in a Tupperware box with a small hole to admit the camera lens.  We attached it to the balloon using four strings taped onto the box with white duct tape.  The four strings led to a fishing swivel to keep the payload from spinning too much.  We clipped it to the strings on the envelope using a black carabiner. Total payload weight was 800 grams (1.75 lbs).


Payload with the lid off, showing flight instrumentation (left) and payload just before launch (right).

We kept an eye on the wind profiles for about 5 months, since most of the time winds in the troposphere blow out to sea.  Finally, we waterproofed the payload and decided to risk a flight even if some winds were going east.  On May 29, the winds in the troposphere were pretty low (max 10 m/s or so) and with varying azimuth.  The stratosphere had a steady breeze going west, so we figured if we made it that high, we’d head back over land.  However, we had a much faster ascent rate and reached a much higher altitude than we anticipated, so we ended up not flying very far from the launch site.  You can download the trajectory data in text format here or Google Earth KML here.


Ground flight path (left), launch site is the origin. Altitude versus time (centre), local time was GMT – 4. Ascent rate versus time (right), local time was GMT – 4. I obtained the ascent rate by calculating a 1 minute moving average and dividing elevation by time.

The photos and video were very good quality considering that we were using the Raspberry Pi camera module (not the world’s most advanced camera).  The troposphere was pretty misty, and it seems like we even passed through a haze layer on the way up.  However, it could be that the lens fogged up temporarily.  Once we entered the stratosphere, the pictures are much better:


The view from 22 kilometres in the sky.

You can watch a slide show of all the photos here, and all the video clips stitched together here.

Just as we approached neutral buoyancy, the payload unexpectedly separated from the envelope and fell back down to Earth.  We kept GPS tracking until about 18 kilometres elevation, and as far as I can tell the box was falling at about 320 km/hr (200 mph).  The Arduino kept track of time even after losing GPS fix, continuing to record until the moment of impact.  Thus, we know that the payload fell the remaining 18 km in about 12 minutes.  The impact speed was probably less than 100 km/hr (60 mph).  The Tupperware payload box was cracked, and everything except the SPOT tracker stopped working.  Had we landed 10 centimetres or so west, we would have splashed down in a pond.  Luckily, we hit the mud on the pond’s edge (and missed the cows that were in the area).


The payload box at the impact site in Snow Camp, North Carolina.

We consider this flight mostly successful.  Our main objectives were to launch, recover, and extract data from an instrument package lifted with a solar balloon.  Furthermore, we wanted to inflate the solar balloon by hand.  Both of these objectives were reached. Our secondary goals were to fly until sunset, not land in the ocean, and make it to the stratosphere.  The flight was only about 2.5 hours, so we did not fly all day as we hoped.  However, we made it well into the stratosphere and were never in danger of ending up in the ocean.

We had a slightly tense launch when the payload got snagged on the eaves of a nearby building, but the balloon built up enough lift to detach itself in about 20 seconds.  The SPOT tracker did not record any positions during flight, so we did not know where the balloon was and were not even sure if the tracker was working.  In fact, it did not record positions until about an hour and a half after impact. The unexpected flight termination was upsetting as we did not anticipate having the payload detach from the envelope.  We assumed that either the envelope would rupture due to sun-induced heating at altitude or that it would deflate at sunset.  In either case, the payload would have had a large plastic streamer to slow it down to safe velocities.  Instead, it appears that the black duct tape we used to attach the payload strings to the envelope got too hot in the intense sunlight at 22 km.  This caused the payload to come loose from the balloon.  The lesson we learned from this is to never use dark coloured tape if there’s a chance the flight system will make it to extreme elevations.

How to install rNOMADS with GRIB file support on Windows

Two years ago, I wrote a software package for R called “rNOMADS” that interfaces with online weather and sea ice model repositories to gather data in real time, for free.  The data are delivered in two ways: a simple, pure R, cross platform interface using GrADS-DODS, and binary files in GRIB format.  The one issue with GRIB is that this format can’t be read directly into R; it requires the external program “wgrib2.”  Installing rNOMADS with GRIB support for Linux is covered in this post (Mac OS is probably similar).  I thought GRIB support for Windows was impossible until the guy who runs this blog casually told me he’d figured it out.  So, I finally got around to trying it myself, and I’m happy to say I got it to work!  Here’s how:

Step 1:

Download the most recent wgrib2.exe version and all DLL files from Wesley Ebisuzaki’s web site:  Click here.

Step 2:

Make a directory somewhere on your computer.   I chose:  C:\Program Files\wgrib2

Copy the .exe and the .dll files to this directory.

Step 3:

Append the directory path to the Windows PATH variable.  Find out how to do this here.

Step 4:  If R is open, close and reopen it.  Then try the following command in the R interpreter:

If you get a bunch of text that looks like the image below, you’ve succeeded.  You can start reading GRIB files with rNOMADS!

Output of wgrib2 call from R.

Output of wgrib2 call from R.

rNOMADS package has been published in the journal Computers & Geosciences

I’m happy to announce that my article “Near Real Time Weather and Ocean Model Data Access with rNOMADS” was recently accepted for publication in the journal Computers & Geosciences. The editor has kindly provided me with a link to the article that’s free until late April, check it out here.  I’m excited to have the package published somewhere official and I’m looking forward to seeing who ends up using it.

In other news, I’m still waiting for the tropospheric jet to point anywhere besides East so we can launch a tracked, camera bearing solar balloon from Chapel Hill.   I wrote an rNOMADS script that generates wind profiles from the 0600 GMT model run on today’s date out to three days from now, and put it on my computer’s crontab.  Every day, I look at the winds, and wait.  Here’s the profiles for today, let’s see if it updates daily on wordpress too:

Wind profiles above Chapel Hill, NC, USA

You can have a look at the script that generates this image here.

Note that the “montage” system command uses the Ubuntu imagemagick package to combine separate PNGs.  If you’re on windows or mac, this will not work for you.

New updates to the rNOMADS package and big changes in the GFS model

I rolled out a big update to the rNOMADS package in R about two weeks ago.  Now, the list of real time weather, ocean, and sea ice models available through rNOMADS updates automatically by scraping the NOMADS web site.  This way, changes in model inventories will be instantly reflected in rNOMADS without the need for a new version release.

Keep abreast of future updates to rNOMADS by subscribing to the mailing list here.  Feel free to ask for help or make comments on this list as well.

In other news, NOAA just updated the Global Forecast System to provide 0.25 x 0.25 degree output – doubling the resolution of the model!  Check out this crystal clear views of surface temperatures across the planet (source code below the image):

World temperature at 2 m above ground using the 0.25 x 0.25 degree output of the Global Forecast System model.

World temperature at 2 m above ground using the 0.25 x 0.25 degree output of the Global Forecast System model.



#Get dates of model output
model.urls <- GetDODSDates(“gfs_0p25”)

#Find day of most recent model run
latest.model <- tail(model.urls$url, 1)

#Find most recent model run on that day
model.runs <- GetDODSModelRuns(latest.model)

#Get the most recent model (excluding analysis only) <- tail(model.runs$[which(grepl(“z$”, model.runs$], 1)

#Define model domain
time <- c(0,0) #Analysis model
lon <- c(0, 1439) #All longitude points
lat <- c(0, 720) #All latitude points
variables <- c(“tmp2m”) #Temperature 2 m above ground

#Get data from NOMADS real time server <- DODSGrab(latest.model,,
variables, time, lon, lat, display.url = FALSE)

#Reformat it
tmp.grid <- ModelGrid(, c(0.25, 0.25), “latlon”)

#Define color scale
colormap <- rev(rainbow(500, start = 0 , end = 5/6))

#Plot it
image(x = tmp.grid$x, y = sort(tmp.grid$y), z = tmp.grid$z[1,1,,], col = colormap,
xlab = “Longitude”, ylab = “Latitude”,
main = paste(“World Temperature at Ground Level:”,

plotGEOmap(coastmap, border = “black”, add = TRUE,
MAPcol = NA)

Fall 2014 Solar Balloon Flights

We had a record number of successful solar balloon launches this fall:  a total of three!  Two of these balloons carried messages in bottles (in case they landed in water) and one just carried a handwritten note.  Unfortunately, no one has come across our messages as of now.  I suspect this means the bottles came down on land somewhere.  Probably some hunter will come across one two decades from now.

We’ve been using paint pigment to darken the balloons – it’s pretty labor intensive because you have to rub the powder into the plastic.  As a result, these balloons were not dark enough and so had pretty bad lift.  Our newest bag (currently under my desk in my graduate student office) is quite a bit darker, and I might give it a once over before I try and send it off.  It’ll be carrying a tracker and a camera, so we’re waiting on light winds before flying.

1.  The 7′ tetroon

Master solar balloon builder Mathew Lippincott sent me a 7′ tetroon to test out earlier last year.  I was pretty excited because I’ve never tried flying a tetroon.  We got it a little dark (but should have spent more time on it), and managed to get it to lift one bottle with a message inside.  Here it is orbiting a parking lot, bouncing off a tree, inching over a busy street at about 30′ elevation, and finally heading skyward:

Thankfully my friend didn’t film the street crossing, because I thought there was going to be a solar balloon/car collision for sure.  Students walking by were pointing and asking if it was a weather balloon.  Hardly!

2.  The 22′ tetroon fail

Not-so-master solar balloon builder glossarch (yours truly) tried to make a tetroon back in 2013.  I made a mistake somewhere down the line and it ended up looking like a giant pillowcase.  Nevertheless, I figured I could get it to fly…and I was right. A bystander thought it was a hang glider.  Come on!  A flying pillowcase holding a Trader Joe’s bag full of bottles resembles no hang glider I’ve ever seen.  To each their own, I guess…

The pillowcase being inflated using vacuum cleaner exhaust.

The pillowcase being inflated using vacuum cleaner exhaust.

3.  Halloween Solar Balloon!

What could be better than a paint dropcloth ghost hovering ominously over your town on Halloween?  I sealed the bag in record time (about 20 minutes) simply by unrolling a swath of paint dropcloth and ironing each edge together to make a cylinder.  Then we drew a scary ghost face on it, scrubbed some pigment on the plastic, and waited for Halloween.  Initially, we had a styrofoam tombstone as payload.  But the tombstone was too heavy, so we ended up just attaching a note and launching.  Because the bottom of the balloon was so poorly ballasted, the whole thing cavorted around in midair quite a bit, even turning sideways a couple of times.  Due to calm winds near the surface we had quite a few witnesses.

Inflating the beast.

Inflating the beast.

Haunting Chapel Hill on Halloween 2014.

Haunting Chapel Hill on Halloween 2014.

A big thanks to Xiao Yang for taking photos and video!  See his Flickr albums here.

Persistence of Vision Frisbee

The first time I saw a persistence of vision (POV) clock I thought: “Wouldn’t that be cool on the side of the frisbee?”. It is a fun weekend project to make one. Here is an action shot of the POV frisbee I made:


It looks better in person – this picture is the best we could do with a Canon camera held on a level surface. The frisbee is spelling out ‘bovine aero’. Below is a picture of a test in a darkened room:


This is the perfect project for Adafruit’s tiny, lightweight Arduino compatible Trinket. The total cost of the materials was around $30:

Assembly is as simple as hooking up an LED & resistor in series to each digital out pin, writing some code to blink the lights in the correct order (see below), and attaching the circuit to the frisbee. Below are some pictures taken during assembly:


Some insights, if anyone else tries this:

  • I considered using a load sensor to determine the rotation rate of the frisbee (a = v^2/r). If you know the rotational speed of the frisbee, you can time the LED outputs to write more consistently spaced letters. However, it turns out the POV illusion looks OK if you use a fixed letter writing rate (I used an ‘on’ time 1 ms per vertical column of letter).
  • To mount the LEDs on the frisbee, I drilled holes in the side and covered them with electrical tape. Punching the LEDs through the tape provided a stable mount for the LEDS.
  • I used letters made up of 5×5 pixel blocks. A better POV display could use more vertically stacked LEDs, or even multicolored LEDs.

Click below to see the code I used:

int B[25] = {1,1,1,1,1, 1,0,1,0,1, 1,0,1,0,1, 0,1,0,1,0, 0,0,0,0,0};
int O[25] = {0,1,1,1,0, 1,0,0,0,1, 1,0,0,0,1, 0,1,1,1,0, 0,0,0,0,0};
int V[25] = {1,1,1,1,0, 0,0,0,1,0, 0,0,0,0,1, 0,0,0,1,0, 1,1,1,1,0};
int I[25] = {0,0,0,0,0, 1,0,0,0,1, 1,1,1,1,1, 1,0,0,0,1, 0,0,0,0,0};
int N[25] = {1,1,1,1,1, 0,1,0,0,0, 0,0,1,0,0, 0,0,0,1,0, 1,1,1,1,1};
int E[25] = {1,1,1,1,1, 1,0,1,0,1, 1,0,1,0,1, 1,0,1,0,1, 0,0,0,0,0};
int A[25] = {0,1,1,1,1, 1,0,1,0,0, 1,0,1,0,0, 0,1,1,1,1, 0,0,0,0,0};
int R[25] = {1,1,1,1,1, 1,0,1,0,0, 1,0,1,1,0, 0,1,0,1,1, 0,0,0,0,0};
int S[25] = {0,1,0,0,1, 1,0,1,0,1, 1,0,1,0,1, 1,0,0,1,0, 0,0,0,0,0};
int P[25] = {1,1,1,1,1, 1,0,1,0,0, 1,0,1,0,0, 0,1,0,0,0, 0,0,0,0,0};
int C[25] = {0,1,1,1,0, 1,0,0,0,1, 1,0,0,0,1, 1,0,0,0,1, 0,0,0,0,0};
int space[25] = {0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0};

int letter_time;
int write_time;

void setup() {
  //use pins 0-4 for output
  pinMode(0, OUTPUT);
  pinMode(1, OUTPUT);
  pinMode(2, OUTPUT);
  pinMode(3, OUTPUT);
  pinMode(4, OUTPUT);

  letter_time = 2; //delay between letters (ms)
  write_time = 1; //how long a led is activated for (ms)

//Write an letter to the POV display
void write_letter(int * letter) {
  //write letter, column by column
  for(int i = 0; i < 5; i++) {
    for(int j = 0; j < 5; j++) {
      digitalWrite(j, letter[j+i*5]);

  //write space after letter
  for(int i = 0; i < 5; i++) {
    digitalWrite(i, 0);

void loop() {

rNOMADS 2.0.2 released

I uploaded the newest version of rNOMADS to CRAN yesterday. This one has a new plotting function for wind altitude azimuth, and magnitude (see below for plot and source code). I also added a function for reading GRIB inventories, fixed a few typos, and tweaked a few functions. In other news, the rNOMADS subversion repository has migrated to R-Forge:

I’ve also set up an rNOMADS mailing list; subscribe here if you need to ask for help or want to hear about new releases.

Wind profiles at each infrasound station in the Transportable Array (currently on the US East Coast). Ground surface is at the center of the plot, the top of the stratosphere is at the outer radius. Wind magnitudes are denoted by color, and azimuth by position around the circle.

Wind profiles at each infrasound station in the Transportable Array (currently on the US East Coast). Ground surface is at the center of the plot, the top of the stratosphere is at the outer radius. Wind magnitudes are denoted by color, and azimuth by position around the circle.


destfile = "myTA.RDATA")
#Find the latest Global Forecast System model run
model.urls <- GetDODSDates("gfs_0p50")
latest.model <- tail(model.urls$url, 1)
model.runs <- GetDODSModelRuns(latest.model) <- tail(model.runs$, 1)

#Get model nodes

lons <- seq(0, 359.5, by = 0.5)
lats <- seq(-90, 90, by = 0.5)
lon.ind <- which(lons <= max(myTA$lon + 360) & lons >= min(myTA$lon + 360)) - 1
lat.ind <- which(lats <= max(myTA$lat) & lats >= min(myTA$lat)) - 1
levels <- c(0, 46)
time <- c(0, 0)

#Get data <- DODSGrab(latest.model,, "hgtprs",
    time, c(min(lon.ind), max(lon.ind)), c(min(lat.ind), max(lat.ind)), levels) <- DODSGrab(latest.model,, "ugrdprs",
    time, c(min(lon.ind), max(lon.ind)), c(min(lat.ind), max(lat.ind)), levels) <- DODSGrab(latest.model,, "vgrdprs",
    time, c(min(lon.ind), max(lon.ind)), c(min(lat.ind), max(lat.ind)), levels)

#Reformat the data
hgt.grid <- ModelGrid(, c(0.5, 0.5))
ugrd.grid <- ModelGrid(, c(0.5, 0.5))
vgrd.grid <- ModelGrid(, c(0.5, 0.5))

#Build profiles
zonal.wind <- c()
meridional.wind <- c()
height <- c()
for(k in 1:length(myTA$lon)) {
     hgt.profile <- BuildProfile(hgt.grid,
         myTA$lon[k], myTA$lat[k], spatial.average = TRUE)
    ugrd.profile <- BuildProfile(ugrd.grid,
        myTA$lon[k], myTA$lat[k], spatial.average = TRUE)
    vgrd.profile <- BuildProfile(vgrd.grid,
        myTA$lon[k], myTA$lat[k], spatial.average = TRUE)
    synth.hgt <- seq(min(hgt.profile),
        max(hgt.profile), length.out = 1000)
    ugrd.spline <- splinefun(hgt.profile, ugrd.profile, method = "natural")
    vgrd.spline <- splinefun(hgt.profile, vgrd.profile, method = "natural")
    zonal.wind[[k]] <- ugrd.spline(synth.hgt)
    meridional.wind[[k]] <- vgrd.spline(synth.hgt)
    height[[k]] <- synth.hgt
     print(paste("Finished", k, "of", length(myTA$lon), "profiles!"))

#Plot them all
PlotWindProfile(zonal.wind, meridional.wind, height, lines = TRUE, points = FALSE,
    elev.circles = c(0, 25000, 50000), elev.labels = c(0, 25, 50), radial.lines = seq(45, 360, by = 45),
    colorbar = TRUE, invert = FALSE, point.cex = 2, pch = 19, lty = 1, lwd = 1,
    height.range = c(0, 50000), colorbar.label = "Wind Speed (m/s)", = 15)