Initial import of foxy Fox Hunting Google Maps App
authordrowe67 <drowe67@01035d8c-6547-0410-b346-abe4f91aad63>
Sat, 25 May 2013 05:19:41 +0000 (05:19 +0000)
committerdrowe67 <drowe67@01035d8c-6547-0410-b346-abe4f91aad63>
Sat, 25 May 2013 05:19:41 +0000 (05:19 +0000)
git-svn-id: https://svn.code.sf.net/p/freetel/code@1271 01035d8c-6547-0410-b346-abe4f91aad63

README.txt [new file with mode: 0644]
cgi-bin/addbearing.cgi [new file with mode: 0755]
cgi-bin/cleardb.cgi [new file with mode: 0755]
cgi-bin/delbearing.cgi [new file with mode: 0755]
cgi-bin/getbearings.cgi [new file with mode: 0755]
foxy.css [new file with mode: 0644]
foxy.html [new file with mode: 0644]

diff --git a/README.txt b/README.txt
new file mode 100644 (file)
index 0000000..0803375
--- /dev/null
@@ -0,0 +1,126 @@
+README.txt for foxy
+David Rowe
+May 25 2013
+
+
+Introduction
+------------
+
+Simple Google Maps application to plot Fox Hunt bearings.
+
+* Screen shot: foxy_screenshot.png
+
+* Based on Google maps V3 API.
+
+* Light weight: On the server we have just a few CGIs and a text file
+  database.  A browser cookie is used for storing configuration
+  information.  The CGIs are a few lines of shell script, so can run
+  on any machine.  Installation involves copying a few files and
+  setting a few permissions.
+
+
+Using Foxy
+----------
+
+1/ Point your browser at http://yourserver/foxy.html
+
+2/ Enter the bearing in the text box and left click to add a bearing.
+
+3/ Left click on a bearing to get information.  Right click to delete
+a bearing.
+
+
+Implementation Notes
+--------------------
+
+Foxy is implemented in Javascript (foxy.html).  A simple text database
+file /var/www/foxy/bearings.txt is used to store bearing
+information. Very simple 1 page CGIs written in shell script are used
+to access the bearing database.
+
+A browser cookie is used to store config information like the current
+map centre.
+
+All the CGI scripts assume a hard coded path of /var/www/bearings for
+the bearings.txt database.
+
+
+Files
+-----
+
+  foxy.html        - Javascript code for Foxy
+  foxy.css         - style sheet
+
+"cgi-bin" directory:
+
+  addbearing.cgi   - adds a bearing to bearings.txt
+  delbearing.cgi   - deletes a bearing from bearings.txt
+  getbearings.cgi   - reads bearings.txt database
+  cleardb.cgi      - clears database, removing all bearings
+
+
+Software
+--------
+
+You need:
+
+1/ A web server. Apache is assumed in the /usr/lib/cgi-bin path below.
+   The paths may be different for other web servers.  Note the path
+   to /var/www/foxy is hard coded in the CGI scripts so it's
+   best not to change that.
+
+
+Installation
+------------
+
+1/ Server PC
+
+  $ svn co https://freetel.svn.sourceforge.net/svnroot/freetel/foxy
+  $ cd foxy
+  $ sudo mkdir /var/www/foxy
+  $ sudo chmod 777 /var/www/foxy
+  $ cp foxy.css foxy.html /var/www/foxy
+  $ sudo cp cgi-bin/* /usr/lib/cgi-bin
+
+Tests
+-----
+
+1/ Test reading and writing bearings.txt database with your browser:
+
+   http://localhost/cgi-bin/addbearing.cgi?lat=123&lng=456&bearing=90
+
+   $ cat /var/www/foxy/bearings.txt
+   123,456,90
+
+   http://localhost/cgi-bin/delbearing.cgi?lat=123&lng=456
+
+   $ cat /var/www/foxy/bearings.txt
+   (empty file)
+
+Debugging
+---------
+
+1/ Monitor Apache log:
+
+  $ tail -f /var/log/apache2/access.log
+
+2/ Use Firebug on Firefox to single step, set breakpoints etc.
+
+3/ Check bearings.txt database, each line is (lat, lng, IP):
+
+  # cat /var/www/foxy/bearings.txt 
+
+  -34.88548650005714,138.55225324630737,90
+  -34.88006501016277,138.55394840240479,180
+  -34.87893842193011,138.55278968811035,265
+  -34.882511765792316,138.55210304260254,10
+
+4/ The "foxy001" cookie stores our state. On Firefox 3.5 you
+   can remove the foxy cookie using Edit-Preferences-Privacy, then
+   click on "remove individual cookies".
+
+5/ To manually reset to defaults:
+   
+   * move to another page (cookie is saved when we exit page)
+   * rm -f /var/www/foxy/bearings.txt
+   * Delete cookie using step (4) above
diff --git a/cgi-bin/addbearing.cgi b/cgi-bin/addbearing.cgi
new file mode 100755 (executable)
index 0000000..95f95c6
--- /dev/null
@@ -0,0 +1,31 @@
+#!/bin/sh
+# addbearing.cgi
+# David Rowe 25 May 2013
+#
+# CGI to add a new bearing
+
+cat <<EOF
+Content-type: text/html
+
+<html>
+<head>
+<meta http-equiv="cache-control" content="no-cache">
+<meta http-equiv="pragma" content="no-cache">
+<meta http-equiv="expires" content="-1"></head></head>
+<body>
+EOF
+
+lat=`echo "$QUERY_STRING" | sed -n "s/.*lat=\(.*\)&lng.*/\1/p"`
+lng=`echo "$QUERY_STRING" | sed -n "s/.*lng=\(.*\)&bearing.*/\1/p"`
+bearing=`echo "$QUERY_STRING" | sed -n "s/.*bearing=\(.*\)/\1/p"`
+
+echo $lat,$lng,$bearing >> /var/www/foxy/bearings.txt
+
+echo $QUERY_STRING "<br>"
+echo "<br>"
+echo $lat $lng $bearing
+
+cat <<EOF
+</body>
+</html>
+EOF
diff --git a/cgi-bin/cleardb.cgi b/cgi-bin/cleardb.cgi
new file mode 100755 (executable)
index 0000000..1d39234
--- /dev/null
@@ -0,0 +1,26 @@
+#!/bin/sh
+# cleardb.cgi
+# David Rowe 25 May 2013
+#
+# CGI to delete entire bearing database
+
+cat <<EOF
+Content-type: text/html
+
+<html>
+<head>
+<meta http-equiv="cache-control" content="no-cache">
+<meta http-equiv="pragma" content="no-cache">
+<meta http-equiv="expires" content="-1"></head></head>
+<body>
+EOF
+
+# path to node database text file
+
+P=/var/www/foxy
+rm $P/bearings.txt
+
+cat <<EOF
+</body>
+</html>
+EOF
diff --git a/cgi-bin/delbearing.cgi b/cgi-bin/delbearing.cgi
new file mode 100755 (executable)
index 0000000..19168df
--- /dev/null
@@ -0,0 +1,36 @@
+#!/bin/sh
+# delbearing.cgi
+# David Rowe 25 May 2012
+#
+# CGI to delete a bearing
+
+cat <<EOF
+Content-type: text/html
+
+<html>
+<head>
+<meta http-equiv="cache-control" content="no-cache">
+<meta http-equiv="pragma" content="no-cache">
+<meta http-equiv="expires" content="-1"></head></head>
+<body>
+EOF
+
+lat=`echo "$QUERY_STRING" | sed -n "s/lat=\(.*\)&.*/\1/pg"`
+lng=`echo "$QUERY_STRING" | sed -n "s/.*lng=\(.*\)/\1/pg"`
+
+# path to node database text file
+
+P=/var/www/foxy
+
+cat $P/bearings.txt | sed "/$lat,$lng.*/ d" > $P/bearings.tmp
+cp $P/bearings.tmp $P/bearings.txt
+rm $P/bearings.tmp
+
+#echo $QUERY_STRING "<br>"
+#echo "<br>"
+#echo $lat $lng
+
+cat <<EOF
+</body>
+</html>
+EOF
diff --git a/cgi-bin/getbearings.cgi b/cgi-bin/getbearings.cgi
new file mode 100755 (executable)
index 0000000..05f7c47
--- /dev/null
@@ -0,0 +1,30 @@
+#!/bin/sh
+# getbearings.cgi
+# David Rowe 25 May 2013
+#
+# CGI to return list of bearings from the bearings.txt database text file
+# We use a CGI rather than fetching the text file directly
+# so we can stop Firefox from caching
+
+cat <<EOF
+Content-type: text/html
+
+<html>
+<head>
+<meta http-equiv="cache-control" content="no-cache">
+<meta http-equiv="pragma" content="no-cache">
+<meta http-equiv="expires" content="-1">
+</head>
+<body>
+EOF
+
+BEARINGS=/var/www/foxy/bearings.txt
+
+if [ -f $BEARINGS ] ; then 
+  cat $BEARINGS
+fi
+
+cat <<EOF
+</body>
+</html>
+EOF
diff --git a/foxy.css b/foxy.css
new file mode 100644 (file)
index 0000000..d72344f
--- /dev/null
+++ b/foxy.css
@@ -0,0 +1,70 @@
+html,body{
+       margin:0;
+       padding:0;
+       width:100%;
+       height:100%;
+       font-family:Arial, Helvetica, sans-serif;
+}
+
+div#header{    
+       vertical-align:middle;
+       border-bottom:1px solid #000;
+}
+div#main-map{
+       width:70%;
+       height:100%;
+       float:left;
+}
+div#side{
+       width:30%;
+       float:left;
+}
+
+div#dataPanel{
+       width:90%;
+       height:50%;
+       overflow:auto;
+       border:2px solid #DDDDDD;
+}
+input{
+       width:90%;
+}
+
+input.navi{
+       font-size:12px;
+       height:30px;
+       margin-bottom:10px;
+}
+div ul{
+       margin-top:30px;
+       margin-bottom:30px;
+}
+div ul li{
+       display: inline;
+       list-style-type: none;
+       padding-right: 40px;
+       font-size:18px;
+       font-weight:bold;
+}
+
+div ul li.title{
+       font-size:22px;
+       color:#888;
+}
+
+div#header p{
+       color:#888;
+       font-size:14px;
+       padding-left:20px;
+}
+span.instruction{
+       font-weight:bold;
+       
+}
+
+.message-box { text-align: center; padding: 5px; color:#545454; width:80%;  margin:5px auto; font-size:12px;}
+.clean { background-color: #efefef; border-top: 2px solid #dedede; border-bottom: 2px solid #dedede; }
+.info  { background-color: #f7fafd; border-top: 2px solid #b5d3ff; border-bottom: 2px solid #b5d3ff; }
+.ok    { background-color: #d7f7c4; border-top: 2px solid #82cb2f; border-bottom: 2px solid #82cb2f; }
+.alert { background-color: #fef5be; border-top: 2px solid #fdd425; border-bottom: 2px solid #fdd425; }
+.error { background-color: #ffcdd1; border-top: 2px solid #e10c0c; border-bottom: 2px solid #e10c0c; }
\ No newline at end of file
diff --git a/foxy.html b/foxy.html
new file mode 100644 (file)
index 0000000..10bbced
--- /dev/null
+++ b/foxy.html
@@ -0,0 +1,443 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta name="viewport" content="initial-scale=1.0, user-scalable=no" />
+<meta http-equiv="content-type" content="text/html; charset=UTF-8"/>
+<style type="text/css">
+  html { height: 100% }
+  body { height: 100%; margin: 0px; padding: 0px }
+  #map_canvas { height: 100% }
+</style>
+<title>Foxy</title>
+
+<link rel="stylesheet" type="text/css" href="foxy.css">
+<script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false"></script>
+
+<script type="text/javascript">
+  /*
+     foxy.html
+     David Rowe 
+     May 25 2013
+
+     Simple Google Maps application to plot Fox Hunt bearings.
+
+     See README.txt for more information.
+     
+     search/grep on string "idea" for future ideas documented in
+     comments below.
+
+  */
+
+  var map;
+
+  // cookie params
+
+  var cookiename = "foxy0001";
+  var cookieexpy = 7;
+
+  // these are stored in cookie, with default values
+
+  var lat = 0;
+  var lng = 0;
+  var myzoom = 2;
+  var update_enable = 0;
+  var debug_enable  = 1;  
+  var known_location = true;
+  var bearing = 0.0;
+
+  var bearings  = []; // database of bearings we keep in memory
+                      // mirrors breaings.txt
+
+
+  // Called when we load page
+
+  function initialize() {
+
+    // we use our cookie as a mini-databse for map location, 
+    // control panel values etc
+    getCookie();
+
+    var myLatlng = new google.maps.LatLng(lat, lng);
+    var myOptions = {
+      zoom: myzoom,
+      center: myLatlng,
+      scaleControl: true,
+      mapTypeId: google.maps.MapTypeId.ROADMAP
+    }
+    map = new google.maps.Map(document.getElementById("main-map"), myOptions);
+
+    google.maps.event.addListener(map, 'click', function (event) {
+       bearing = parseFloat(document.control_panel.bearing.value);
+       placeMarker(event.latLng, bearing, false);
+    });
+
+    document.control_panel.debug_enable.checked = debug_enable;
+
+    // read from bearings.txt database text file on server and init map
+    
+    downloadUrl("/cgi-bin/getbearings.cgi", loadBearings);
+  }
+
+
+  // Slurp up bearings.txt when web pages loads
+
+  loadBearings = function(doc, status) {
+    log("loadBearings start");
+
+    // split the document into lines
+
+    var lines = doc.split("\n");
+    for (var i=0; i<lines.length; i++) {
+      if (lines[i].length > 1) {
+
+        // split each line into parts separated by "," and use the contents
+
+        parts = lines[i].split(",");
+        if (parts.length > 1) {
+          var lat = parseFloat(parts[0]);
+          var lng = parseFloat(parts[1]);
+          var bearing = parseFloat(parts[2]);
+
+          addNewBearing(lat, lng, bearing, false);
+        }
+      }
+    }
+
+    log("loadBearings end");
+
+  }
+
+
+  // remove leading and trailing whitespace from a string
+
+  function trim(stringToTrim) {
+       return stringToTrim.replace(/^\s+|\s+$/g,"");
+  }
+
+
+  // calculate distance between two points
+
+  rad = function(x) {return x*Math.PI/180;}
+
+  distHaversine = function(p1, p2) {
+    var R = 6371; // earth's mean radius in km
+    var dLat  = rad(p2.lat() - p1.lat());
+    var dLong = rad(p2.lng() - p1.lng());
+
+    var a = Math.sin(dLat/2) * Math.sin(dLat/2) +
+            Math.cos(rad(p1.lat())) * Math.cos(rad(p2.lat())) * Math.sin(dLong/2) * Math.sin(dLong/2);
+    var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
+    var d = R * c;
+
+    return d.toFixed(3);
+  }
+
+
+  // sets up map for a new bearings
+  // adds bearing to bearings[] array but doesn't write to text file
+
+  function addNewBearing(lat, lng, bearing, isNewBearing) {
+
+    // create the marker at point where bearing starts
+
+    var location = new google.maps.LatLng(lat, lng);
+    var newMarker = new google.maps.Marker(
+      {position: location, 
+       map: map});
+
+    // idea: might be useful fore "new" bearing indicator
+    // if (isNewBearing) {
+    //  newMarker.setAnimation(google.maps.Animation.BOUNCE);
+    //}
+
+    // add to bearings array
+    // idea: add time stamp to info window?  Signal strength? Order
+
+    var newInfoWindow = new google.maps.InfoWindow(
+      { content: "put some info here"
+      });
+    var newBearing = {marker : newMarker, 
+                      bearing: newBearing, 
+                      infowindow: newInfoWindow};
+    bearings.push(newBearing); 
+
+    // draw bearing as polyline 
+
+    var location2 =  bearingToLocation(location, bearing, 100000);
+    var coords = [
+      location,
+      location2
+    ];
+    var path = new google.maps.Polyline({
+            path: coords,
+            strokeColor: "#080000",
+            strokeOpacity: 0.5,
+            strokeWeight: 5
+    });
+    path.setMap(map);
+
+    google.maps.event.addListener(newMarker, "click", function() {
+
+      for (var j=0; j<bearings.length; j++) {
+        if (bearings[j].marker.position == newMarker.position) {
+          bearings[j].infowindow.open(map, newMarker);
+        }
+      }    
+
+    });
+
+    google.maps.event.addListener(newMarker, "rightclick", function() {
+
+      // remove bearing from bearings array
+
+      var new_bearings = [];
+      for (var j=0; j<bearings.length; j++) {
+        if (bearings[j].marker.position != newMarker.position) {
+          new_bearings.push(bearings[j]);
+        }
+      }
+      bearings = new_bearings;
+
+      // delete bearing from text file
+
+      var url;
+      url = "/cgi-bin/delbearing.cgi?" + "lat=" +  this.position.lat() + "&" + "lng=" +  this.position.lng();
+      downloadUrl(url, function(doc) { });
+      this.setMap(null);
+
+    });
+
+  }
+
+
+  // save state (config info) to our cookie
+
+  function setCookie() {
+    var cookietext = cookiename;
+    cookietext += "=" + map.getCenter().lat();
+    cookietext += "|" + map.getCenter().lng() + "|" + map.getZoom();
+    cookietext += "|" + debug_enable;
+    if (cookieexpy) {
+      var exdate=new Date();
+      exdate.setDate(exdate.getDate()+cookieexpy);
+      cookietext += ";expires="+exdate.toGMTString();
+    }
+    
+    // write the cookie
+
+    document.cookie = cookietext;
+  }
+
+
+  // Grab a bunch of config info from our cookie
+
+  function getCookie() {
+                       
+    if (document.cookie.length > 0) {
+      cookieStart = document.cookie.indexOf(cookiename + "=");
+
+      // lets see if our cookie exists
+
+      if (cookieStart!=-1) {
+        cookieStart += cookiename.length+1; 
+        cookieEnd=document.cookie.indexOf(";",cookieStart);
+        if (cookieEnd==-1) {
+          cookieEnd=document.cookie.length;
+        }
+        cookietext = document.cookie.substring(cookieStart,cookieEnd);
+        // split the cookie text and create the variables
+
+        bits = cookietext.split("|");
+        lat = parseFloat(bits[0]);
+        lng = parseFloat(bits[1]);
+        myzoom = parseInt(bits[2]);
+        debug_enable = parseInt(bits[6]);
+      }
+    } 
+  }
+
+  
+  // Converts numeric degrees to radians
+
+  function toRad(deg) {
+    return deg * Math.PI / 180;
+  }
+
+  // Converts radians to numeric (signed) degrees
+
+  function toDeg(rad) {
+    return rad * 180 / Math.PI;
+  }
+  // given a bearing and distance calculate end location
+
+  function bearingToLocation(location1, bearing, d) {
+    var lat1 = toRad(location1.lat());
+    var lon1 = toRad(location1.lng());
+    var bearing_rad = toRad(bearing);
+    var R = 6378137;
+
+    var lat2 = Math.asin( Math.sin(lat1)*Math.cos(d/R) + 
+              Math.cos(lat1)*Math.sin(d/R)*Math.cos(bearing_rad) );
+    var lon2 = lon1 + Math.atan2(Math.sin(bearing_rad)*Math.sin(d/R)*Math.cos(lat1), 
+                     Math.cos(d/R)-Math.sin(lat1)*Math.sin(lat2));
+
+    var location2 = new google.maps.LatLng(toDeg(lat2), toDeg(lon2));
+    return location2;    
+  }
+                            
+  // called when we click to add a bearing on map
+
+  function placeMarker(location, bearing, isNewBearing) {
+    if (isNewBearing == undefined) {
+      isNewBearing = false;
+    }
+
+    addNewBearing(location.lat(), location.lng(), bearing, isNewBearing);
+
+    // save to bearings database by calling a CGI
+
+    var url;
+    url = "/cgi-bin/addbearing.cgi?" + "lat=" + location.lat() + "&" + "lng=" + location.lng() + "&" + "bearing=" + bearing;
+    downloadUrl(url, function(doc) { });
+  }
+  
+  /**
+  * Returns an XMLHttp instance to use for asynchronous
+  * downloading. This method will never throw an exception, but will
+  * return NULL if the browser does not support XmlHttp for any reason.
+  * @return {XMLHttpRequest|Null}
+  */
+  function createXmlHttpRequest() {
+    try {
+      if (typeof ActiveXObject != 'undefined') {
+        return new ActiveXObject('Microsoft.XMLHTTP');
+      } else if (window["XMLHttpRequest"]) {
+        return new XMLHttpRequest();
+      }
+     } catch (e) {
+     changeStatus(e);
+     }
+   return null;
+  };
+
+  /**
+  * This functions wraps XMLHttpRequest open/send function.
+  * It lets you specify a URL and will call the callback if
+  * it gets a status code of 200.
+  * @param {String} url The URL to retrieve
+  * @param {Function} callback The function to call once retrieved.
+  */
+  function downloadUrl(url, callback) {
+    var status = -1;
+    var request = createXmlHttpRequest();
+    if (!request) {
+      return false;
+    }
+
+    request.onreadystatechange = function() {
+      if (request.readyState == 4) {
+        try {
+          status = request.status;
+        } catch (e) {
+          // Usually indicates request timed out in FF.
+        }
+        if (status == 200) {
+          callback(request.responseText, request.status);
+          request.onreadystatechange = function() {};
+        }
+      }
+    }
+    request.open('GET', url, true);
+    try {
+      request.send(null);
+    } catch (e) {
+      changeStatus(e);
+    }
+  };
+
+  function log(message) {
+    if (debug_enable == 1) {
+      message_list = document.getElementById("debugging_messages");
+      log_message = document.createTextNode(message);
+      log_list_item = document.createElement('li');
+      log_list_item.appendChild(log_message);
+      message_list.appendChild(log_list_item);
+    }
+  }
+
+  function clearDatabase() {
+
+    // call CGI to rm bearings.txt
+
+    downloadUrl("/cgi-bin/cleardb.cgi",function(doc){});
+  }
+
+  function debugClicked() {
+    if (document.control_panel.debug_enable.checked)
+      debug_enable = 1;
+    else
+      debug_enable = 0;
+  }
+
+</script>
+
+</head>
+<body onload="initialize()" onunload="setCookie()">
+
+  <div id="main-map">
+  </div>
+
+  <div id="side">
+
+    <h3>Help</h3>
+      <table>
+        <tr><td width="5%"></td><td><a href="https://freetel.svn.sourceforge.net/svnroot/freetel/foxy/README.txt">Foxy README</a></td></tr>
+      </table>
+
+    <div id="control" style="width: 100%;">
+      <h3>Contol Panel</h3>
+      <form name="control_panel">
+        <table>
+       <tr>
+          <td width="5%">
+          <td>Bearing</td>
+          <td colspan="2"><input type="text" name="bearing" value="0" size="15"></td>
+        </tr>
+        <tr>
+          <td></td>
+         <td>Clear Database</td>
+          <td></td>
+          <td width="30%"></td>
+          <td width="60"><input type="button" value="Clear" onclick="clearDatabase()"></td>
+        </tr>
+        <tr>
+         <td></td>
+          <td>Debug Messages</td>
+          <td><input type="checkbox" name="debug_enable" onclick="debugClicked()"></td>
+        </tr>
+        </table>
+      </form>
+
+
+    <div id="tests" style="width: 100%;">
+      <h3>Tests</h3>
+      <ol>
+       <li><a href="/cgi-bin/getbearings.cgi">Test bearings.txt database</a><br>
+      </ol>
+    </div>
+
+    <div id="debugging_output" style="width: 100%;">
+      <h3>Debugging Output</h3>
+      <ol id="debugging_messages">
+      </ol>
+    </div>
+  </div>
+
+
+</body>
+</html>