GOB File for the Bicycle Ride Calorie Calculator

Below follows the example GOB source code for the calculator. This code, when run through the GOB GObject preprocessor, will generate all of the C code for a GObject. The DWI code just sets and gets the parameters on this thing, invoking the subroutine at the bottom to compute the calories burned.

/*
 * FILE:
 * bike-calc.gob
 *
 * FUNCTION:
 * Implements the core of the Bicycle Ride Calorie Calculator
 * using "gob" (http://www.5z.com/jirka/gob.html) to simplify 
 * the creation of GObjects.  This is a re-write of the old 
 * "calcalc" Bicycle Ride Calorie Calculator from Greg Kondrasuk
 * <kondrag@geocities.com>, which was written in FLTK and is at
 * http://www.geocities.com/SiliconValley/Vista/6434/calcalc.html
 *
 * This code does two basic things:
 * 1) Declares an object with half-dozen input paramters, and 
 *    three output paramters.
 * 2) Defines a subroutine to calculate the calories burned 
 *    (the output), based on the input paramters.
 *
 * Even if you've never seen a 'gob' created before, this file 
 * is still simple enough to understand.
 *
 * HISTORY:
 * Copyright (c) 2004 Linas Vepstas <linas@linas.org> April 2004
 */

requires 2.0.0

%{
#include <libintl.h>
#include <math.h>
#include <stdio.h>
#include "bike-calc.h"
#include "bike-calc-private.h"

#define _(X) gettext(X)

#define KM 0.62137 // kilometers per mile
#define KG 2.20462 // kilograms per pound
#define MT 3.28084 // meters per foot
%}

class Bike:Calc from G:Object 
{
	/* Inputs to the calculator */
	/* ----------  Ride Data ----------------- */
	private float distance=0.0;
	property FLOAT distance
		(nick = "distance",
		 blurb = _("Ride Distance"),
		 minimum = 0.0,
		 export,
		 link);

	private int time_interval=0.0;
	property INT time_interval
		(nick = "time interval",
		 blurb = _("Ride Time"),
		 minimum = 0,
		 export,
		 link);

	private float draft=0.0;
	property FLOAT draft
		(nick = "draft",
		 blurb = _("Percentage of Time Spent Drafting"),
		 minimum = 0.0,
		 maximum = 100.0,
		 export,
		 link);

	private float climb=0.0;
	property FLOAT climb
		(nick = "climb",
		 blurb = _("Percentage of Time Spent Climbing"),
		 minimum = 0.0,
		 maximum = 100.0,
		 export,
		 link);

	private float elevation=0.0;
	property FLOAT elevation
		(nick = "elevation",
		 blurb = _("Elevation Gain"),
		 minimum = 0.0,
		 export,
		 link);

	private gboolean loop=1;
	property BOOLEAN loop
		(nick = "loop",
		 blurb = _("Loop if True, else a Point-to-Point Ride"),
		 export,
		 link);

	private gboolean aero=1;
	property BOOLEAN aero
		(nick = "aero",
		 blurb = _("If True, then Aerodynamic Position"),
		 export,
		 link);

	private int wind_direction = 0;
	property INT wind_direction
		(nick = "wind_direction",
		 blurb = _("Wind Direction"),
		 export,
		 link);

	private float wind_speed=0.0;
	property FLOAT wind_speed
		(nick = "wind_speed",
		 blurb = _("Wind Speed"),
		 minimum = 0.0,
		 maximum = 200.0,
		 export,
		 link);

	/* ----------  Rider Data ----------------- */
	private float weight=100.0;
	property FLOAT weight
		(nick = "weight",
		 blurb = _("Rider Weight"),
		 default_value = 100.0,
		 minimum = 0.0,
		 maximum = 1000.0,
		 export,
		 link);

	/* ----------  Units (English/Metric) ----------------- */
	private gboolean metric_units = 0;
	property BOOLEAN metric_units
		(nick = "metric_units",
		 blurb = _("Use Metric Units if set"),
		 default_value = 0,
		 export)
		set {
			gboolean use_mu = g_value_get_boolean (VAL);
			if (self->_priv->metric_units != use_mu)
			{
				if (use_mu)
				{
					self->_priv->distance /= KM;
					self->_priv->elevation /= MT;
					self->_priv->wind_speed /= KM;
					self->_priv->weight /= KG;
				}
				else
				{
					self->_priv->distance *= KM;
					self->_priv->elevation *= MT;
					self->_priv->wind_speed *= KM;
					self->_priv->weight *= KG;
				}
			}
			self->_priv->metric_units = use_mu;
		}
		get {
			g_value_set_boolean (VAL, self->_priv->metric_units);
		};

	/* output from the calculator */
	/* ----------  Ride Statistics Output ----------------- */
	private float avg_speed=0.0;
	property FLOAT avg_speed
		(nick = "avg speed",
		 blurb = _("Average Speed"),
		 minimum = 0.0,
		 maximum = 100.0,
		 export)
		set {}
		get {
			self_compute (self);
			g_value_set_float (VAL, self->_priv->avg_speed);
		};

	private float total_calories=0.0;
	property FLOAT total_calories
		(nick = "total calories",
		 blurb = _("Total Calories"),
		 minimum = 0.0,
		 maximum = 10000.0,
		 export,         // XXX this should be read-only
		 link);

	private float ride_calories=0.0;
	property FLOAT ride_calories
		(nick = "ride calories",
		 blurb = _("Ride Calories"),
		 minimum = 0.0,
		 maximum = 10000.0,
		 export,         // XXX this should be read-only
		 link);



	public GObject *
	new (void)
	{
		return (GObject *) GET_NEW;
	}

	private void
	compute (self)
	{
		printf ("Object Values:\n");
		printf ("\tdistance=%f\n", self->_priv->distance);
		printf ("\ttime=%d\n", self->_priv->time_interval);
		printf ("\tdraft=%f\n", self->_priv->draft);
		printf ("\tclimb=%f\n", self->_priv->climb);
		printf ("\televaction=%f\n", self->_priv->elevation);
		printf ("\tloop=%d\n", self->_priv->loop);
		printf ("\taero=%d\n", self->_priv->aero);
		printf ("\twind_direction=%d\n", self->_priv->wind_direction);
		printf ("\twind_speed=%f\n", self->_priv->wind_speed);
		printf ("\tweight=%f\n", self->_priv->weight);
		printf ("\n");

		self->_priv->total_calories = 2.0;

		/* basic setup */
		float time_min = ((float) self->_priv->time_interval) / 60.0;
		float distance = self->_priv->distance;
		float weight = self->_priv->weight;
		float avg_speed = 60.0 * (distance / time_min);
		float climb = self->_priv->climb;
		int loop = self->_priv->loop;
		int aero = self->_priv->aero;
		float elev_gain = self->_priv->elevation;
		float draft = self->_priv->draft;
		int wind_direction = self->_priv->wind_direction;
		float wind_speed = self->_priv->wind_speed;
		gboolean use_metric = self->_priv->metric_units;

		if (use_metric)
		{
			distance   *= KM;
			weight     *= KG;
			avg_speed  *= KM;  // convert to miles per hour
			elev_gain  *= MT;
			wind_speed *= KM;
		}

		/* Computations.
		 * Copyright (C) 1998  Greg Kondrasuk (kondrag@geocities.com)
		 * Greg originally wrote:
		 * >> The formula for this is a direct translation of the
		 * >> calorie calculation worksheet that appeared in an article
		 * >> published in the May 1989 issue of Bicyling Magazine, 
		 * >> pp. 100-103.
       * I translated his formulas to those below, hopefully without
		 * introducing errors.  Looks to me like these formulas expect
		 * english units (feet, miles, pounds)
		 */

		// calculate the baseline value
		float baseline = (8.79618E-6 * avg_speed * avg_speed * avg_speed
		             - 1.46998E-4 * avg_speed * avg_speed
		             + 0.00359 * avg_speed
		             + 0.00556
		           ) * weight;

		float modBase = baseline;

		// surface area adjustment
		modBase -= ((weight-154.0) / 200.0) * baseline;

		// terrain adjustment
		float terrainAdj = (climb / 1000.0) * modBase;

		// need to do terrain adjustment for a point to point ride
		if (0 == loop)
		{
			terrainAdj += weight * elev_gain * 0.0014 / time_min;
			modBase += terrainAdj;
		}

		// Wind Adjustment
#define Headwind        0
#define CrossHeadwind   1
#define CrossWind       2
#define CrossTailwind   3
#define Tailwind        4

		if (0 == loop)
		{
			float airspeed=0.0;
			switch (wind_direction)
			{
				case Headwind:
				case CrossHeadwind:
					airspeed = avg_speed + 0.5 * wind_speed;
					break;

				case Tailwind:
				case CrossTailwind:
					airspeed = avg_speed - 0.5 * wind_speed;
					break;
			}
			float tempBase = (8.79618E-6 * airspeed * airspeed * airspeed
						- 1.46998E-4 * airspeed * airspeed 
						+ 0.00359 * airspeed + 0.00556) * weight;
			switch (wind_direction)
			{
				case Headwind:
				case Tailwind:
					modBase += tempBase - baseline;
					break;
				case CrossHeadwind:
				case CrossTailwind:
					modBase += 0.7*(tempBase - baseline);
					break;
			}
		}

		// Riding Position adjustment
		if ((15.0 < avg_speed) && (0 == aero))
		{
			modBase += (-0.66893 + 0.0467*avg_speed) * modBase;
		}

		// Drafting Adjustment
		modBase -= (draft / 100.0) * (avg_speed / 100.0);

		// total calorie expenditure
		float totalCals = modBase * time_min;

		// Natural caloric expenditure
		modBase = modBase - 0.01 * weight;
		float rideCals = modBase * time_min;

		/* copy to the widget */
		if (use_metric)
		{
			avg_speed  /= KM;  // convert back to km per hour
		}
		self->_priv->avg_speed = avg_speed;
		self->_priv->total_calories = totalCals;
		self->_priv->ride_calories = rideCals;
	}
}