--- /dev/null
+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
--- /dev/null
+<!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>