How to install wgrib2 in OSX

Prompted by both my own struggles with wgrib2 compilation and a plea on the rNOMADS email listserv, I’m going to describe how to compile and install wgrib2 on Mac OS.

First of all, some background: wgrib2 is an excellent utility written by Wesley Ebisuzaki at NOAA.  It allows for a number of swift and stable operations on GRIB2 files (a common file format for weather and climate data).  It is also a requirement for using grib files in rNOMADS (the function ReadGrib() in particular).

So here’s how to install it on Mac OS.

  1.  Get Command Line Tools for Xcode (search for it on Duckduckgo or your engine of choice) and also make sure gcc is installed. If you don’t know what gcc is, stop now and find someone who does (it will save you a lot of time).
  2. Download wgrib2 here (note download links are pretty far down the page).
  3. Untar the tarball somewhere, and roll up your sleeves.  cd into the resulting wgrib directory.
  4.  In the makefile, uncomment the lines

    #export cc=gcc
    #export FC=gfortran
    Also search for makefile.darwin in the makefile and uncomment the line containing it.  You’ll see instructions to this effect in the makefile anyway.
  5. Now we have to edit the included libpng package, since it is untarred by the makefile and doesn’t inherit our compiler specifications in step 4. Ensuring that we’re in the wgrib directory:

    tar -xvf libpng-1.2.57.tar.gz
    cd libpng-1.2.57/scripts/

    now edit the makefile.darwin file, changing

    CC=cc

    to

    CC=gcc

    Now, return to the wgrib directory, and re-tar libpng!

    tar -cf libpng-1.2.57.tar libpng-1.2.57
    gzip libpng-1.2.57.tar

    If it asks you if you want to replace the original tar.gz file, say “yes”.  What we’ve done here is edited libpng to make sure it uses the right compiler.
  6. Finally, type make to build wgrib2.

If you are still having problems (for example, libaec complaining that there is no c compiler), make sure that all the compiler commands (gcc, cc, etc.) all point to something other than clang (the default compiler that comes with OSX).  You may have to edit your bash_profile file to ensure this.

As always, contact me on the form below if you’re having unresolvable issues.

Accessing ensemble weather models with rNOMADS

Downloading a weather model and using it to predict stuff is all well and good – but what are the sources of error?  Specifically, how accurate is the atmospheric parametrization?   Luckily, NOMADS provides sets of models with slightly different initialization values.  The result is a set of realizations that captures the variability of the model product.  Here’s how to get that product, and examples of what you can do with it.


library(rNOMADS)

#Get the latest ensemble model run
model.urls <- GetDODSDates("gens")
latest.model <- tail(model.urls$url, 1)
model.runs <- GetDODSModelRuns(latest.model)
model.run <- tail(model.runs$model.run[grepl("all",
    model.runs$model.run)], 1)

#Define region of interest: Chapel Hill, NC
lon <- -79.052104
lat <- 35.907553

#Set up DODS model grid indices
#Use GetDODSModelRunInfo() to figure out how to do this for other products
lons <- seq(0, 359, by = 1)
lats <- seq(-90, 90, by = 1)

lon.diff <- abs(lon + 360 - lons)
lat.diff <- abs(lat - lats)

model.lon.ind <- which(lon.diff == min(lon.diff)) - 1
model.lat.ind <- which(lat.diff == min(lat.diff)) - 1

#Set up call to NOMADS GrADS-DODS server
#Analysis(?) model only
time <- c(0, 0)
#Longitude grid
node.lon  <- c(model.lon.ind - 2, model.lon.ind + 2)
#Latitude grid
node.lat  <- c(model.lat.ind - 2, model.lat.ind + 2)
#Temperature, wind speeds, and geopotential height
variables <- c("tmpprs", "ugrdprs", "vgrdprs", "hgtprs")
levels    <- c(0, 25) #All available levels
ensemble <- c(0, 20)  #All available ensembles

model.data <- DODSGrab(latest.model, model.run, variables,
    time, node.lon, node.lat, levels = levels, ensemble = ensemble)

#Plot temperature variability
plot(c(-65, 25), c(0, 35), xlab = "Temperature (C)",
    ylab = "Geopotential Height (km)", type = 'n')

for(k in ((ensemble[1]:ensemble[2] + 1))) {
    model.data.sub <- SubsetNOMADS(model.data, ensemble = c(k),
        variables = c("hgtprs", "tmpprs"))
    profile <- BuildProfile(model.data.sub, lon, lat)
    points(profile[[1]]$profile.data[,
        which(profile[[1]]$variables == "tmpprs"), 1] - 272.15,
        profile[[1]]$profile.data[,
        which(profile[[1]]$variables == "hgtprs"), 1] / 1000)
}

#Plot winds
zonal.wind <- NULL
merid.wind <- NULL
height     <- NULL

for(k in ((ensemble[1]:ensemble[2] + 1))) {
    model.data.sub <- SubsetNOMADS(model.data,
        ensemble = c(k), variables = c("hgtprs",
        "ugrdprs", "vgrdprs"))
    profile <- BuildProfile(model.data.sub, lon, lat)
    hgt     <- profile[[1]]$profile.data[,
        which(profile[[1]]$variables == "hgtprs"),]
    ugrd    <- profile[[1]]$profile.data[,
        which(profile[[1]]$variables == "ugrdprs"),]
    vgrd    <- profile[[1]]$profile.data[,
        which(profile[[1]]$variables == "vgrdprs"),]

   synth.hgt <- seq(min(hgt),
       max(hgt), length.out = 1000)
   ugrd.spline <- splinefun(hgt, ugrd, method = "natural")
   vgrd.spline <- splinefun(hgt, vgrd, method = "natural")
   zonal.wind[[k]] <- ugrd.spline(synth.hgt)
   merid.wind[[k]] <- vgrd.spline(synth.hgt)
   height[[k]] <- synth.hgt
}

dev.new()
PlotWindProfile(zonal.wind, merid.wind, height, lines = TRUE,
    points = FALSE, elev.circles = c(0, 15000, 30000),
    elev.labels = c(0, 15, 30), radial.lines = seq(45, 360, by = 45),
    colorbar = TRUE, invert = FALSE,
    point.cex = 2, pch = 19, lty = 1, lwd = 1,
    height.range = c(0, 30000), colorbar.label = "Wind Speed (m/s)")

Here’s atmospheric profiles from GFS ensemble runs on November 24, 2016 for Chapel Hill, NC. Temperature is quite variable at low altitudes (several degrees Celsius) and at 5 km above sea level (a couple degrees). Ensemble runs are more consistent for the rest of the atmosphere.

chapel_hill_temperature

Temperature profile above the Geology Department at UNC Chapel Hill for November 24, 2016 at 18:00 UTC.

Wind speeds are pretty consistent, at least at the resolution of the color scale I’m using.  Wind direction varies by a few degrees below 15 km, but becomes quite a bit less reliable between 15 and 30 km altitude.  This is one reason that our solar balloon trajectories are so hard to predict.  The other reason is because we still don’t really understand the physics of solar balloon flight, but that’s a subject for another post.

chapel_hill_wind_profile

Predicting the Flight Path of a Solar Balloon

Balloons that fly on solar power – what could be better?  Unfortunately, the physics of these balloons is complex and reliable data on how they fly is hard to come by. On this blog, we’ve discussed our attempt to come up with a numerical model of solar balloon flight as well as the data set from our solar balloon that made it to 22 km (72,000 ft) last May.  In this post, we present an empirically-derived solar balloon flight model based on data we collected on the high altitude flight mentioned above.

First off: a disclaimer.  This model is generated from data collected by one flight of a specific design of solar balloon.  While we hope that it captures some general features of a solar balloon flight, we can’t be sure.  Furthermore, different balloon designs and atmospheric conditions will cause inevitable (and at this point, unpredictable) deviations from the model we describe.  Take it with a big grain of salt.

Ascent Rate

We smoothed the GPS altitude data from our high altitude launch and performed a 1st order Tikhonov regularization to derive an ascent rate versus altitude model for our solar balloon.  The plot is below, if you want the numbers click here.

Modeled ascent rate versus actual ascent rate for our high altitude solar balloon.

Modelled ascent rate versus actual ascent rate for our high altitude solar balloon.

The solar balloon initially ascends at about 1.5 meters per second, reaches a maximum of about 2.5 meters per second at around 15 km elevation (just below the tropopause), and rapidly decreases as the balloon approaches neutral buoyancy at 22 km elevation.  This is fortuitous, since the strongest winds in this elevation profile typically exist in the region where the balloon is ascending the fastest.  In contrast, the lower stratosphere (18-22 km) is usually calmer.  The exciting possibility in this trajectory data is that we may be able to park future balloons in this low wind “sweet spot”, and thus recover payloads relatively close to the launch site.

The modelled ascent rate lacks the vertical velocity oscillations in the real data; this is by design.  We suspect those oscillations are either due to gravity waves in the atmosphere or uneven heating of the balloon envelope.  In any case, we would not expect to see the exact same ones on future flights.   Furthermore, removing the oscillations has little effect on the accuracy of the ascent rate model, as the plot below shows.

Modeled elevation vs actual elevation for the May 29, 2015 high altitude solar balloon flight.

Modeled elevation vs actual elevation for the May 29, 2015 high altitude solar balloon flight.

However, a comparison between the actual ground track of the balloon versus one using horizontal winds calculated from archived weather forecast data shows a considerable deviation:

Modelled versus actual ground path of the high altitude solar balloon.

Modelled versus actual ground path of the high altitude solar balloon.

This may be because of the poor spatial resolution of the archived Global Forecast System model we used (1 x 1 degree) as well as the lack of a precise match between the prediction time and the flight time.  Although the two trajectories are rotated, their general form is similar.  While the forecast skill is not ideal, it still gives a reasonable approximation of what we could have expected on launch day.  To this end, we are utilizing the simple vertical velocity model derived above in combination with predicted wind speeds to generate potential flight paths.  Here’s an example from September 28, 2015:

Example of a modelled solar balloon flight path. The green diamond is the launch site, the red line is the ascent, the dashed line is 8 hours of drifting at neutral buoyancy, and the blue circle is the predicted balloon location around sunset.

Example of a modelled solar balloon flight path. The green diamond is the launch site, the red line is the ascent, the dashed line is 8 hours of drifting at neutral buoyancy, and the blue circle is the predicted balloon location around sunset.

It may not be perfect, but it sure beats what we used to use.

Downloading weather, sea ice, and wave model data with the rNOMADS package in R

The NOAA Operational Model Archive and Distribution System is a treasure trove of near real time and archived model outputs describing global and regional weather, sea ice, and wave data.  I developed the rNOMADS package about a year ago to make this available to R users. In this post, I’ll present some source code and a couple of figures showing a few of the useful things you can do with rNOMADS.

For detailed examples showing rNOMADS with GRIB file support (Linux only) see the vignette here, for a cross platform version of the same, see here.

1.  Getting wind speed at a specific point

library(rNOMADS)

#A location near my house
lat <- 35.828304
lon <- -79.107467

#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)
latest.model.run <- tail(model.runs$model.run, 1)

#Get nearest model nodes
lons <- seq(0, 359.5, by = 0.5)
lats <- seq(-90, 90, by = 0.5)
lon.diff <- abs(lon + 360 - lons)
lat.diff <- abs(lat - lats)
model.lon.ind <- which(lon.diff == min(lon.diff)) - 1 #NOMADS indexes at 0
model.lat.ind <- which(lat.diff == min(lat.diff)) - 1

#Subset model
time <- c(0,0) #Model status at initialization
lon.inds <- c(model.lon.ind - 2, model.lon.ind + 2)
lat.inds <- c(model.lat.ind - 2, model.lat.ind + 2)
variables <- c("ugrd10m", "vgrd10m") #E-W and N-S wind

wind.data <- DODSGrab(latest.model, latest.model.run, variables,
time, lon.inds, lat.inds)

profile <- BuildProfile(wind.data, lon, lat, spatial.average = TRUE, points = 4)

#Present results!
print(paste("At", profile[[1]]$forecast.date, "the East-West winds at Briar Chapel were going", sprintf("%.2f", profile[[1]]$profile.data[1, which(profile[[1]]$variables == "ugrd10m"), 1]),
"meters per second, and the north-south winds were going", sprintf("%.2f", profile[[1]]$profile.data[1, which(profile[[1]]$variables == "vgrd10m"), 1]),
"meters per second."))

#How did I know all these strange parameter names?
info <- GetDODSModelRunInfo(latest.model, latest.model.run)
print(info)

2. Getting a temperature profile from 0 to 40 km above a specific point

library(rNOMADS)

#A location near my house
lat <- 35.828304
lon <- -79.107467

#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)
latest.model.run <- tail(model.runs$model.run, 1)

#Get nearest model nodes
lons <- seq(0, 359.5, by = 0.5)
lats <- seq(-90, 90, by = 0.5)
lon.diff <- abs(lon + 360 - lons)
lat.diff <- abs(lat - lats)
model.lon.ind <- which(lon.diff == min(lon.diff)) - 1 #NOMADS indexes at 0
model.lat.ind <- which(lat.diff == min(lat.diff)) - 1

#Subset model
time <- c(0,0) #Model status at initialization
lon.inds <- c(model.lon.ind - 2, model.lon.ind + 2)
lat.inds <- c(model.lat.ind - 2, model.lat.ind + 2)
levels <- c(0, 46) #All pressure levels
variables <- c("tmpprs", "hgtprs") #First get temperature

model.data <- DODSGrab(latest.model, latest.model.run, variables,
time, lon.inds, lat.inds, levels)

#Interpolate to the point of interest
profile <- BuildProfile(model.data, lon, lat,
spatial.average = TRUE, points = 4)

#Plot it!
tmp <- profile[[1]]$profile.data[,which(profile[[1]]$variables == "tmpprs"),] - 272.15
hgt <- profile[[1]]$profile.data[,which(profile[[1]]$variables == "hgtprs"),]

plot(tmp, hgt, xlab = "Temperature (C)",
ylab = "Geopotential Height", main = paste("Temperature above Chapel Hill, NC, at",
profile[[1]]$forecast.date))
Temperature profile of troposphere and stratosphere above Chapel Hill, NC, on May 28.

Temperature profile of troposphere and stratosphere above Chapel Hill, NC, on May 28.

2. A world map of surface temperature

library(GEOmap)
library(rNOMADS)

model.urls <- GetDODSDates("gfs_0p50")
latest.model <- tail(model.urls$url, 1)
model.runs <- GetDODSModelRuns(latest.model)
latest.model.run <- tail(model.runs$model.run, 1)

time <- c(0,0) #Analysis model
lon <- c(0, 719) #All 720 longitude points
lat <- c(0, 360) #All 361 latitude points

tmp2m.data <- DODSGrab(latest.model, latest.model.run,
"tmp2m", time, lon, lat)
atmos <- ModelGrid(tmp2m.data, c(0.5, 0.5))
colormap <- rev(rainbow(500, start = 0 , end = 5/6))
image(atmos$x, sort(atmos$y), atmos$z[1,1,,], col = colormap,
xlab = "Longitude", ylab = "Latitude",
main = paste("World Temperature at Ground Level:", atmos$model.run.date))
plotGEOmap(coastmap, border = "black", add = TRUE,
MAPcol = NA)

World temperature on May 28.

World temperature on May 28.

2. Wave heights in the northwest Atlantic ocean

library(rNOMADS)
library(GEOmap)

model.urls <- GetDODSDates("wave")
latest.model <- tail(model.urls$url, 1)
model.runs <- GetDODSModelRuns(latest.model) #West Atlantic waves
latest.model.run <- tail(model.runs$model.run, 1)
time <- c(0,0)
lon <- c(0, 274)
lat <- c(0, 202)

wave.data <- DODSGrab(latest.model, latest.model.run,
"htsgwsfc", time, lon, lat)
wave.grid <- ModelGrid(wave.data, c(0.25, 0.25))
#Remove "no data" values
wave.grid$z[which(wave.grid$z>1e10, arr.ind=TRUE)] <- NA
colormap <- rainbow(500, start=0, end=5/6)
image(wave.grid$x, sort(wave.grid$y), wave.grid$z[1,1,,], col = colormap,
xlab = "Longitude", ylab = "Latitude",
main = paste("Wave Height:", wave.grid$model.run.date))
plotGEOmap(coastmap, border = "black", add = TRUE,
MAPcol = "black")

Wave heights in the north Atlantic on July 4th, 2014. High seas from Hurricane Arthur are visible just off the US East Coast.

Accessing global weather model data using the rNOMADS package in R

*** UPDATE ***

rNOMADS 2.0.0 was released yesterday (5-15-2014), and it now has cross-platform support (meaning you can use its data retrieval capabilities in Windows and Mac as well as Linux).

Stay tuned for more tutorials and usage examples in the coming days.

***

The rNOMADS package interfaces with the NOAA Operational Model Archive and Distribution System to provide access to 60 operational (i.e. real time and prediction) models describing the state of the ocean and the atmosphere. rNOMADS has been used to get wind and wave data for a real time sailing game, to quantify solar energy available for power plants in Europe, and to predict helium balloon flights. We look forward to continuing to hear about novel and unexpected uses for this spectacular data set.

In this post, we show how to use rNOMADS to do the following:
1. Plot global temperature and wind at different atmospheric pressure levels
2. Produce a precise atmospheric temperature and wind profile for a specific point at a specific time

Links to source code are provided below each set of figures. A link to rNOMADS installation instructions is provided at the end of the post.

Global Temperature and Wind Maps

The Global Forecast System (GFS) model provides weather data on a 0.5 x 0.5 degree grid for the entire planet.  It is run 4 times daily, and produces a prediction every three hours out to 180 hours.  The zero hour “analysis” forecast is the state of the atmosphere at the model run time, and it uses some observational data to increase its accuracy.  Here, we plot the temperature at 2 m above the surface, the wind at 10 m above the surface, and the winds at 300 mb (around 9 kilometers elevation).  The 300 mb plot often shows the northern and southern jet streams quite well.

Temperature at the Earth's surface determined using the Global Forecast System model.

Temperature at the Earth’s surface determined using the Global Forecast System model.

Winds at the surface of the Earth from the GFS model.  Note the little spot of high winds - that's Tropical Cyclone Gillian, a Category 3 storm when this image was generated.

Winds at the surface of the Earth from the GFS model. Note the little spot of high winds south of Indonesia – that’s Tropical Cyclone Gillian, a Category 3 storm when this image was generated.

Jet streams and Rossby waves are clearly visible in this image of the wind speeds of the upper troposphere/lower stratosphere.

Jet streams and Rossby waves are clearly visible in this image of the wind speeds of the upper troposphere/lower stratosphere.

Download the source code for these images here.

 

Instantaneous Atmospheric Profile over Sakura-Jima volcano, Japan

It’s important to know which direction the winds are going directly above active volcanoes, because eruptions can carry ash into air space and over inhabited areas.  One impetus for the continued development of rNOMADS was to provide a one-stop solution for generating high precision wind profiles over specific points, allowing ash distribution prediction as soon as an eruption begins.  Here, we have generated a spatially and temporally interpolated wind and temperature profile over Sakura-Jima volcano, Japan.  The profile is calculated for the exact time when the source code is run.

Instantaneous temperature profile above Sakura-jima volcano, Japan.

Instantaneous temperature profile above Sakura-jima volcano, Japan.

An eruption at this instant would produce ashfalls east of the volcano for a plume height of 15 km.  However, if a truly massive 30 km high plume was produced, ashfalls would occur both east and west of the volcano.

An eruption at this instant would produce ashfalls east of the volcano for a plume height of 15 km. However, if a truly massive 30 km high plume was produced, ashfalls would occur both east and west of the volcano.

Download the source code for these images here.

 

Some of these scripts require the aqfig package in R to generate the colorbar legends.

Instructions on installing R, rNOMADS, and their dependencies are here.

Trajectory of the Qu8ke Rocket

Back in 2011, the Qu8ke rocket created quite a buzz with this amazing video from its flight:

One of the goals of this rocket was to win the Carmack Prize, an award for the first amateur rocket to obtains a GPS reading from over 100,000 ft. While this rocket did break the 100,000 ft mark, it unfortunately lost GPS lock during its high speed ascent. Analysis of the on board accelerometer data indicates that the rocket reached ~121,000 ft. In a previous post, we describe a method for determining the position a high altitude photograph was taken from using a set of landmarks, OpenCV, and information about the camera’s field of view. The video from the flight of the Qu8ke rocket provides an excellent opportunity to revisit this method and to confirm the accelerometer predicted elevation.

Step 1: Find the GoPro camera parameters

The Qu8ke rocket carried a GoPro camera, which recorded video of the flight in 1080p. To analyze this video with OpenCV, we first needed to find the camera matrix, which contains information about the field of view and radial distortion (fisheye) of the lens.  Following OpenCv’s  method for finding the camera matrix from a set of pictures with a known geometry (we used a chess board), we found that it was difficult to obtain a matrix that could reliably undistort an entire image taken by a GoPro. This may be due to the fact that OpenCV only uses three radial distortion coefficients, which are unable to fully describe the distortion in the image resulting from the 170 degree field of view lens. Despite this, the camera matrix is able to accurately describe mapping between points near the center of the image, as shown below.  We avoided this issue by only picking landmarks not close to the edges/corners of the image.

My beautiful picture

Original (left), Undistorted (Right)

Step 2: Extract a series of stills from Youtube video

We downloaded the source mp4 video from youtube with the 4K video downloader and extracted pictures using ffmpeg. For example the following command:

ffmpef -ss 100 -i qu8ke.mp4 -r 1 -qscale:v 2 %05.jpg

extracts relatively high quality jpegs from the movie every 1 s, starting 100 s into the video. ImageMagick’s convert tool was used to rotate, crop, and resize batches of images.

Step 3: Use OpenCV with a set of landmarks visible in the images to estimate the camera position:

Although the video from the flight is 1080p, the extracted still images aren’t very high quality due to motion blur and the melted plastic that accumulated on the lens (things get hot when you are going Mach 1+). A picture from the rocket near the apogee is shown below:

pnp_1The black circles are the human picked landmark locations, while the blue circles are the reprojected landmark locations given the predicted camera position and orientation. The fact that the circles overlap demonstrate that we have a self-consistent estimate of the camera pose. Adding small amounts of noise to the camera matrix and 2D/3D landmark locations, we are able to estimate the uncertainty in the camera pose. For most of the images we analyzed, the uncertainty in the position was below 6,000 ft. Using pictures throughout the flight like the one above, we reconstructed the trajectory of the rocket:

quake_flightOur predictions confirm that the Qu8ke rocket surpassed 100,000 ft in elevation, and indicate that the rocket went a bit higher (~13,000 ft) than predicted by the on board accelerometers. I am not an expert in interpreting accelerometer data, but I would guess that integrating the acceleration data over the course of the rocket flight becomes increasingly prone to error as time increases, which could account for the difference in the elevation predictions.

Solar Balloon Buoyancy and Trajectory Predictions

So you’ve built a solar balloon. But will it even fly? And how high will it go? This post outlines a method to model the buoyancy and 1D trajectory (elevation vs. time) of a solar balloon. Quite a few formulas go into the complete the model, so a full description is left to a longer PDF report, which is available here. Additionally, the code written to compute the trajectory is available here.

Modeling solar balloon buoyancy is an exercise in heat transfer. Generally, the balloon is heated by the radiation it absorbs and cooled by the fluid flow around it and infrared emission. The sources of radiation absorbed by the balloon include direct solar radiation, reflected and diffuse solar radiation, and infrared radiation emitted by the earth and sky:

radiation_sources

A contour plot of the radiation intensity falling upon a balloon vs. time and elevation on Nov 1 is shown below.  The amount of direct solar radiation reaching the balloon increases with elevation due to the thinning atmosphere, however, the overall radiation intensity is highest near to the ground due to the IR radiation emitted by the Earth.

Rad_Cont

The other important source is heat transfer is due to fluid flow. As described in the report, there are fairly accurate engineering level correlations which can be used to determine the rate of heat transfer from the surface of the balloon to interior and to the exterior atmosphere.  In conjunction with our model of the radiation environment, we use these correlations to determine the balloon buoyancy and ascent rate.

An example of the trajectory of a solar balloon 5 m in diameter with a 3 kg payload is shown below. The balloon takes off slightly after dawn (0 hrs), reaches a peak elevation of ~15 km, and then descends after sunset (indicated by the red line).

bl_el

One interesting prediction of the 1D trajectory model is that certain configurations of solar balloons should be able to fly at night, powered by the infrared flux from the Earth alone. There is precedent for this, as French scientific balloons powered by solar/terrestrial radiation alone have achieved flights of up to 70 days, circumnavigating the globe several times.

There is still a good deal of work to be done on this model. Most importantly a validation against real solar ballon flight data is needed. Additionally, there are a number of issues to be addressed, such as modeling the effects of non-spherical balloon shape and finding a convenient way to determine the optical properties of the balloon film (such as solar absorptivity). We eventually plan on combining this model with rNomads to compute 3D balloon trajectories.