Refactored tracker.js
authorplehegar
Tue, 23 Aug 2011 16:39:44 -0400
changeset 24 a8b41bb10592
parent 20 e08bd9e38afe
child 25 0eb5bcfce248
Refactored tracker.js
Added user profile in tracker.js
Minor update in geolocation.ssp
src/main/resources/scripts/tracker.js
src/main/resources/templates/geolocation.ssp
--- a/src/main/resources/scripts/tracker.js	Tue Aug 23 10:48:47 2011 -0400
+++ b/src/main/resources/scripts/tracker.js	Tue Aug 23 16:39:44 2011 -0400
@@ -1,19 +1,73 @@
 
+var Const = {
+  KM : "km",
+  KM_UNIT : 1,
+  MILE_UNIT : 0.621371192,
+  MILE : "mile",
 
-function send(user, data) {
+  // wait for at least 2 consecutive GPS data before declaring the GPS ready
+  GPS_MAX_ERRORS : 2,
+};
+
+var Util = {
+  store : {
+    enabled : (function() {
+      try {
+       return !!window.localStorage.getItem;
+      } catch (e) {
+       return false;
+      }
+    })(),
+    set : function (key, value) {
+      try {
+        window.localStorage.setItem(key, JSON.stringify(value));
+        return true;
+      } catch (e) { };
+      return false;
+    },
+    get : function (key) {
+      try {
+        var value = window.localStorage.getItem(key);
+        return ((value != null)? JSON.parse(value) : null);
+      } catch (e) { };
+      return null;
+    }
+  },
+
+  log : function (msg) {
+    var element = document.getElementById("log");
+    if (element != null) element.textContent = msg;
+  },
+
+  // pretty formatting of numbers
+  formatNumber : function (n) {
+    return (n < 10)? "0" + n : n;
+  },
+
+  // pretty formatting of time
+  formatTime : function (time) {
+    t = parseInt(time);
+    hours = parseInt(t / 3600);
+    minutes = parseInt(t / 60);
+    seconds = t - ((hours * 3600) + (minutes * 60));
+    return ((hours > 0)?  Util.formatNumber(hours) + ":": "") + Util.formatNumber(minutes) + ":" + Util.formatNumber(seconds);
+  },
+
+  // send information to the server as JSON
+  sendAsJSON : function (uri, data) {
     var xhr = new XMLHttpRequest;
 
     try {
-    xhr.open("POST", "/user/"  + user + "/run", true);
+    xhr.open("POST", uri, true);
     xhr.onreadystatechange = function() {
         if (xhr.readyState == 4) {
             if (xhr.status != 200) { // this needs to be 201
-              log("xhr failed? [" + xhr.status + "] " + xhr.statusText);
+              Util.log("xhr failed? [" + xhr.status + "] " + xhr.statusText);
             } else {
-              log("resource created at " + xhr.getResponseHeader("Location"));
+              Util.log("resource created at " + xhr.getResponseHeader("Location"));
             }
         } else {
-            log("xhr readyState is " + xhr.readyState);
+            Util.log("xhr readyState is " + xhr.readyState);
         }
     };
     xhr.setRequestHeader("Content-Type", "application/json");
@@ -22,133 +76,139 @@
     xhr.send(JSON.stringify(data));
 
     } catch (e) {
-      log("Failed to send to server " + e);
+      Util.log("Failed to send to server " + e);
       return false;
     }
    return true;
-}
-
-var storage = (function() {
-     try {
-       return !!window.localStorage.getItem;
-     } catch (e) {
-       return false;
-     }
-})();
-
-
-
-var watchId = 0;
-var running = false;
-
-var duration = 0;
-var start_time = 0;
-var intervalId = 0;
-
-// wait for at least 2 consecutive GPS data before declaring the GPS ready
-var MAX_ERRORS = 2;
-var got_error = MAX_ERRORS;
+  },
 
-var discarded = 0;
-var locations = 0;
-
-var activity = { activity: "http://dbpedia.org/resource/Running", events: []};
-
-var dst_unit = 1;
-
-function formatNumber(n) {
-  return (n < 10)? "0" + n : n;
-}  
+  geo : {
+    // calculate the distance in km between two GPS locations
+    calculateDistance : function (lat1, lon1, lat2, lon2) {
+      var dLat = (lat2-lat1) * Math.PI / 180;
+      var dLon = (lon2-lon1) * Math.PI / 180;
+      var lat1 = lat1  * Math.PI / 180;
+      var lat2 = lat2 * Math.PI / 180;
+      var a = Math.sin(dLat/2) * Math.sin(dLat/2) +
+        Math.sin(dLon/2) * Math.sin(dLon/2) * Math.cos(lat1) * Math.cos(lat2);
+      // 6371 km is average earth radius 
+      return 6371 * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));      
+    },
+    
+    calculateDistanceCoordinates : function (events) {
+      var result = 0;
+      if (events.length > 0) {
+        var i = 1;
+        while (i < events.length) {
+          var position1 = events[i-1].c;
+          var position2 = events[i].c;
+          result += Util.geo.calculateDistance(position1[1], position1[0], position2[1], position2[0]);
+          i++;
+        }
+      }
+      return result;
+    },
+    // calculate the duration between geolocation coordinates
+    calculateDurationCoordinates : function (events) {
+      var result = 0;
+      if (events.length > 0) {
+        var i = 1;
+        while (i < events.length) {
+          result += (events[i].t - events[i-1].t);
+          i++;
+        }
+      }
+      return result;
+    }
+  }
+};
 
-function formatTime(time) {
- t = parseInt(time);
- hours = parseInt(t / 3600);
- minutes = parseInt(t / 60);
- seconds = t - ((hours * 3600) + (minutes * 60));
- return ((hours > 0)?  formatNumber(hours) + ":": "") + formatNumber(minutes) + ":" + formatNumber(seconds);
-}
+var getUser = function () {
+    function dummyuser() {
+      this.name     =   "dummy";
+      this.weight   = "73";
+      this.height   = "180";
+      this.distance = "1";
+      this.prefs = { weight : "kg", height : "cm", distance : Const.KM };
+    }
+    var n = Util.store.get("track_gps_user");
+    if (n === null) n = new dummyuser();
+    n.getPostRunURI = function() { return "/user/" + this.name + "/run"; }
+    n.getProfileURI = function() { return "/user/" + this.name + "/profile"; }
+    n.setKm = function() { this.distance = Const.KM_UNIT;  this.prefs.distance = Const.KM; }
+    n.setMile = function() { this.distance = Const.MILE_UNIT;  this.prefs.distance = Const.MILE; }
+    n.save = function() {
+      Util.store.set("track_gps_user", this);
+    };
+        
+    return n; 
+};
 
-
+var app = {
 
+ user : getUser(),
 
-function coordinates_event(position) {
+ // identifier for geolocation watch operation
+ watchId : 0,
+
+ // is an activity ongoing?
+ inActivity : false,
+
+ // total real time duration
+ duration : 0,
+ // start_time since start or pause
+ start_time : 0,
+
+ // identifier for setInterval timer operation
+ intervalId : 0,
+
+ // got_errors counts how many we've got
+ got_error : Const.GPS_MAX_ERRORS,
+ 
+ // total of geolocation data discarded
+ discarded : 0,
+
+ // total of geolocation data accepted
+ locations : 0,
+
+ // this will store all the geolocation data and pause
+ currentActivity : { activity: "http://dbpedia.org/resource/Running", events: []},
+
+};
+
+// add a geolocation position into the current activity
+app.coordinates_event = function (activity, position) {
    var events = activity.events;
    events[events.length] =
     { t:position.timestamp, c: [position.coords.longitude, position.coords.latitude, position.coords.altitude] };
 }
 
-function pause_event() {
+// add a pause event into the current activity
+app.pause_event = function (activity) {
    var events = activity.events;
    events[events.length] = { t:Date.now(), s: [ "pause" ] };
 }
-
-function calculateDistance(lat1, lon1, lat2, lon2) {
- var R = 6371; // km
- var dLat = (lat2-lat1) * Math.PI / 180;
- var dLon = (lon2-lon1) * Math.PI / 180;
- var lat1 = lat1  * Math.PI / 180;
- var lat2 = lat2 * Math.PI / 180;
- var a = Math.sin(dLat/2) * Math.sin(dLat/2) +
-        Math.sin(dLon/2) * Math.sin(dLon/2) * Math.cos(lat1) * Math.cos(lat2); 
- var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a)); 
- var d = R * c;
- return d;
-}
-
-function calculateDistanceCoordinates() {
-  var events = activity.events;
-  var result = 0;
-  if (events.length > 0) {
-    var i = 1;
-    while (i < events.length) {
-      var position1 = events[i-1].c;
-      var position2 = events[i].c;
-      result += calculateDistance(position1[1], position1[0], position2[1], position2[0]);
-      i++;
-    }
-  }
-  return result;
-}
-
-function calculateDurationCoordinates() {
-  var events = activity.events;
-  var result = 0;
-  if (events.length > 0) {
-    var i = 1;
-    while (i < events.length) {
-      var position1 = events[i-1].t;
-      var position2 = events[i].t;
-      result += (position2 - position1);
-      i++;
-    }
-  }
-  return result;
-}
-
-function repaint() {
-  if (document.visibilityState == true && running) {
-    document.getElementById("locations").textContent = String(locations);
-    var d = calculateDistanceCoordinates() * dst_unit;
-    var t = duration + (Date.now() - start_time);
+// simply refresh the current screen data
+app.repaint = function () {
+  if (document.visibilityState && app.inActivity) {
+    document.getElementById("locations").textContent = String(app.locations);
+    var d = Util.geo.calculateDistanceCoordinates(app.currentActivity.events) * app.user.distance;
+    var t = app.duration + (Date.now() - app.start_time);
     document.getElementById("distance").textContent = String(Math.round(d*100)/100);
     if (t != 0 && d != 0) {
       document.getElementById("speed").textContent = String(Math.round( (d / (t / 3600000)) * 100) / 100);
       document.getElementById("pace").textContent = String(Math.round( ((t / 60000)/d) * 100) / 100);
     }
-    if (activity.events.length > 0) {
+    if (app.currentActivity.events.length > 0) {
       document.getElementById("gps_text").style.display = "none";
       document.getElementById("gps_data").style.display = "inline";
     }
   }
 }
 
-
-function log(msg) {
-    document.getElementById("log").textContent = msg;
-}
-
-function logGPS(position, startMsg) {
-    log(
+// Display a GPS location in the page
+app.logGPS = function (position, startMsg) {
+    Util.log(
       startMsg + position.timestamp 
        + ": " + position.coords.latitude
        + ", " + position.coords.longitude
@@ -159,31 +219,33 @@
        + ", " + position.coords.speed);
 }
 
-function handleSuccess(position) {
-  if (running) {
-   logGPS(position, "GPS: ");
 
-    // discard positions that have no altitude since they're likely inacurate
+// when watchPosition is successful at returning a GPS coordinate
+app.handleSuccess = function (position) {
+  if (app.inActivity) {
+   app.logGPS(position, "GPS: ");
+
+    // discard positions with no altitude since they're likely inaccurate
     if (position.coords.altitude != null) {
-      coordinates_event(position);
-      locations++;
-      repaint();
+      app.coordinates_event(app.currentActivity, position);
+      app.locations++;
+      app.repaint();
     } else {
-      document.getElementById("discarded").textContent = String(discarded++);
+      document.getElementById("discarded").textContent = String(app.discarded++);
     }
    } else {
 
 
      if (position.coords.altitude == null) {
-       got_error++;
-       if (got_error > MAX_ERRORS) got_error = MAX_ERRORS;
-           logGPS(position, "[ERROR] GPS: ");
+       app.got_error++;
+       if (app.got_error > Util.GPS_MAX_ERRORS) app.got_error = Util.GPS_MAX_ERRORS;
+           app.logGPS(position, "[ERROR] GPS: ");
      } else {
-       got_error--;
-       logGPS(position, "GPS: ");
+       app.got_error--;
+       app.logGPS(position, "GPS: ");
      }
-     if (got_error <= 0) {
-       got_error = 0;
+     if (app.got_error <= 0) {
+       app.got_error = 0;
        document.getElementById("gps_text").textContent = "ready";       
        document.getElementById("waves").style.display = "block";       
      } else {
@@ -193,7 +255,8 @@
    }
 }
 
-function handleError(error) {
+// when watchPosition is not successful at returning a GPS coordinate
+app.handleError = function (error) {
   var code = "unknown";
   switch(error.code) {
     case error.TIMEOUT:
@@ -206,68 +269,68 @@
       code = "position unavailable";
       break;
   }
-  if (running) {
-    document.getElementById("errors").textContent = String(++got_error);
-    log("[ERROR] GPS: " + code); 
+  if (app.inActivity) {
+    document.getElementById("errors").textContent = String(++app.got_error);
+    Util.log("[ERROR] GPS: " + code); 
   } else {
     document.getElementById("gps_text").textContent = code; 
     // wait for MAX_ERRORS non-consecutive errors
-    got_error = MAX_ERRORS;
+    app.got_error = Util.GPS_MAX_ERRORS;
   }
 }
 
-function timer() {
-  if (document.visibilityState == true) {
+// Called by setInterval to display the real time
+app.timer = function () {
+  if (document.visibilityState) {
     document.getElementById("realtime").textContent = 
-      formatTime(Math.round((duration+(Date.now()-start_time))/1000));
+      Util.formatTime(Math.round((app.duration+(Date.now()-app.start_time))/1000));
   }
 }
 
-function start() {
-  if (!running) {
-   running = true;
-   start_time = Date.now();
-   intervalId = setInterval(timer, 1000);
+// The Start / Pause button
+app.start = function () {
+  if (!app.inActivity) {
+    // start an activity
+   app.inActivity = true;
+   app.start_time = Date.now();
+   app.intervalId = setInterval(app.timer, 1000);
    document.getElementById("stop").disabled = false;
    document.getElementById("start").textContent = "Pause";
   } else {
-   running = false;
-   clearInterval(intervalId);
-   duration += (Date.now() - start_time);
+   // pause an activity
+   app.inActivity = false;
+   clearInterval(app.intervalId);
+   app.duration += (Date.now() - app.start_time);
    document.getElementById("start").textContent = "Restart";
-   pause_event();
+   app.pause_event(app.activity);
    document.getElementById("gps_text").style.display = "inline";
    document.getElementById("gps_data").style.display = "none";
   }
 }
 
-function stop() {
-  navigator.geolocation.clearWatch(watchId);
-  running = false;
-  clearInterval(intervalId);
-  got_error = 0;
-  locations = 0;
-  discarded = 0;
-  duration  = 0;
+// The Stop button
+app.stop = function () {
+  app.inActivity = false;
+  navigator.geolocation.clearWatch(app.watchId);
+  clearInterval(app.intervalId);
+  app.got_error = 0;
+  app.locations = 0;
+  app.discarded = 0;
+  app.duration  = 0;
   document.getElementById("start").textContent = "Start";
   document.getElementById("stop").disabled = true;
-  log("");
-  if (storage) {
-    try {
-      window.localStorage.setItem("track_gps_app", JSON.stringify(activity));
-    } catch (e) {
-      log("[ERROR] Can't store the value " + e.code);
+  Util.log("");
+  if (!Util.sendAsJSON(app.user.getPostRunURI(),app.currentActivity)) {      
+    if (!Util.store.set("track_gps_app", app.currentActivity)) {
+      Util.log("[ERROR] Can't store the value " + e.code);
     }
   }
-  if (!send("teole",activity)) {      
-      // do something!      
-  }
-  activity.events = [];
+  app.currentActivity.events = [];
 }
 
-function init() {  
+app.init = function () {  
   if (!!document.visibilityState)
-    document.addEventListener("visibilitychange", repaint, false);
+    document.addEventListener("visibilitychange", app.repaint, false);
   else
     document.visibilityState = true;
   
@@ -275,18 +338,13 @@
   // return false;
 
   // @@ in the future, make sure this is empty
-   if (storage) {
-    window.localStorage.setItem("track_gps_app", "");
-
-    if (window.localStorage.getItem("track_gps_app_dst_unit") === "mile") {
-      dst_unit = 0.621371192;    
-    }
-    setUnit();
+   if (Util.store.set("track_gps_app", "")) {
+    app.setUnit();
    } else {
-     log("[ERROR] no local storage");
+     Util.log("[ERROR] no local storage");
    }
    if (!!navigator.geolocation) {
-     watchId = navigator.geolocation.watchPosition(handleSuccess, handleError, {enableHighAccuracy:true, maximumAge:0, timeout:1000});
+     app.watchId = navigator.geolocation.watchPosition(app.handleSuccess, app.handleError, {enableHighAccuracy:true, maximumAge:0, timeout:1000});
    } else {
      document.getElementById("gps_text").textContent = "not supported";
      document.getElementById("start").disabled = true;
@@ -294,31 +352,26 @@
 
 }
 
-window.addEventListener("load", init, false);
+window.addEventListener("load", app.init, false);
 
-function km() {
-  if (storage) {
-    window.localStorage.setItem("track_gps_app_dst_unit", "km");
-  }
-  dst_unit = 1;
-  setUnit();
-  repaint();
+app.km = function () {
+  app.user.setKm();
+  app.user.save();
+  app.setUnit();
+  app.repaint();
 }
 
-function mile() {
-  if (storage) {
-    window.localStorage.setItem("track_gps_app_dst_unit", "mile");
-  }
-  dst_unit = 0.621371192;    
-  setUnit();
-  repaint();
+app.mile = function () {
+  app.user.setMile();
+  app.user.save();
+  app.setUnit();
+  app.repaint();
 }
 
-function setUnit() {  
-  var text = (dst_unit == 0.621371192)? "mile" : "km";
-  var nodes = document.getElementsByClassName("dst_unit");
+app.setUnit = function () {  
+  var nodes = document.getElementsByClassName("distance_unit");
   i = 0;
   do {
-    nodes[i].textContent = text;
+    nodes[i].textContent = app.user.prefs.distance;
   } while (++i < nodes.length);
 }
--- a/src/main/resources/templates/geolocation.ssp	Tue Aug 23 10:48:47 2011 -0400
+++ b/src/main/resources/templates/geolocation.ssp	Tue Aug 23 16:39:44 2011 -0400
@@ -7,6 +7,8 @@
  <meta name="viewport" content="width=device-width,initial-scale=1.0" />
  <link rel='icon' type='image/png' href='geolocation-icon.png'/>
  <title>HTML5 Track</title>
+ <script src='/scripts/json2.js'>
+ </script>
  <script src='/scripts/tracker.js'>
  </script>
  <link rel="stylesheet" href="/styles/tracker.css" type="text/css" />
@@ -50,16 +52,16 @@
 <table class='text'>
 <tbody>
 <tr>
-<th>Distance</th> <td id='distance'>0</td><td class='dst_unit'>km</td>
+<th>Distance</th> <td id='distance'>0</td><td class='distance_unit'>km</td>
 </tr>
 <tr>
 <th>Time</th><td id='realtime'>00:00</td><td></td>
 </tr>
 <tr>
-<th>Speed</th><td id='speed'>0</td><td><span class='dst_unit'>km</span>/h</td>
+<th>Speed</th><td id='speed'>0</td><td><span class='distance_unit'>km</span>/h</td>
 </tr>
 <tr>
-<th>Pace</th><td id='pace'>0</td><td>min/<span class='dst_unit'>km</span></td>
+<th>Pace</th><td id='pace'>0</td><td>min/<span class='distance_unit'>km</span></td>
 </tr>
 <tr>
 <th>GPS</th><td colspan='2'><span id='gps_text'>not started</span><span id='gps_data'><span id='errors'>0</span>/<span id='discarded'>0</span>/<span id='locations'>0</span></span></td>
@@ -70,10 +72,10 @@
 <section>
 <p>
 <button type='button' id='start'
-  onclick='start()'>Start</button>
+  onclick='app.start()'>Start</button>
 
 <button type='button' id='stop' disabled='disabled'
-  onclick='stop()'>Stop</button>
+  onclick='app.stop()'>Stop</button>
 
 </p>
 
@@ -86,9 +88,9 @@
 <p class='text'>Current unit: <span class='dst_unit'>km</span></p>
 
 <p>
-<button type='button' id='start' onclick='km()'>KM</button>
+<button type='button' id='start' onclick='app.km()'>KM</button>
 
-<button type='button' id='stop' onclick='mile()'>MILE</button>
+<button type='button' id='stop' onclick='app.mile()'>MILE</button>
 </p>
 
 </section>