Introduction
------------
-Simple Google Maps application to plot Fox Hunt bearings.
+Simple Google Maps application to plot Fox Hunt bearings. When run on
+a phone it automatically detects position and compass bearing. Placing
+Bearings can be placed with a mouse click on non-mobile devices. The
+bearings are stored in a text file database so can be shared by anyone
+who can reach the server.
* Screen shot: foxy_screenshot.png
on any machine. Installation involves copying a few files and
setting a few permissions.
+* Foxy will stand alone without the server & CGIs, but bearing storage
+ won't be persistent and bearings can't be shared. Good for demos or
+ when you can't reach the Internet.
+
+BUGS
+----
+
+On mobile devices:
++ Click to add bearings places the bearing at a location offset from the tap location
++ Double click doesn't remove bearings on mobile devices.
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.
+2/ If running on your phone your bearing will be updated
+automatically. Click on add bearing to add a bearing from your
+current position to the map.
+
+3/ The "where Am I" button re-centers the map on your current
+position. This is useful when scroll and zooming moves your position.
+
+4/ The "Click to Add Bearing" check box allow manual placement of
+bearings with a mouse click. Useful for demo and development on
+non-mobile devices such as desktops and laptops. This should be
+unchecked on mobile devices as pinching/scrolling often generates
+spurious "click" events leading to unwanted bearings.
-3/ Left click on a bearing to get information. Right click to delete
-a bearing.
+5/ Double click on a bearing to delete it.
Implementation Notes
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.
+All the CGI scripts assume a hard coded to a path of
+/var/www/foxy/bearings.txt for the bearings.txt database.
Files
-----
- foxy.html - Javascript code for Foxy
+ foxy.html - Javascript & HTML 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
+ getbearings.cgi - reads bearings.txt database
cleardb.cgi - clears database, removing all bearings
<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" src="http://maps.google.com/maps/api/js?sensor=true"></script>
<script type="text/javascript">
var bearings = []; // database of bearings we keep in memory
// mirrors bearings.txt
+ var geolocationSupport;
+ var geolat = 0;
+ var geolng = 0;
+ var hasCompass;
+ var currentHeading;
+
+ //
+ // Initialisation ----------------------------------------------------------------
+ //
// Called when we load page
function initialize() {
- // we use our cookie as a mini-databse for map location,
+ // if geolocation supported get our location
+
+ if(navigator.geolocation) {
+ geolocationSupport = true;
+ navigator.geolocation.getCurrentPosition(function(position) {
+ lat = geolat = position.coords.latitude;
+ lng = geolng = position.coords.longitude;
+ }, function() {
+ geolocationSupport = false;
+ });
+ }
+
+ // if we have a compass use it to get bearing
+
+ testForCompass();
+
+ // we use our cookie as persistant storage for map location,
// control panel values etc
getCookie();
+ // init map
+
var myLatlng = new google.maps.LatLng(lat, lng);
var myOptions = {
+ disableDoubleClickZoom: true,
zoom: myzoom,
center: myLatlng,
scaleControl: true,
}
map = new google.maps.Map(document.getElementById("main-map"), myOptions);
+ // For testing on a desk top it's useful to left click to add
+ // bearings. This sucks on mobile as a pinch or drag can generate a
+ // click event and you get spurious bearings. So it can be disabled
+ // with the 'Click to add bearing' check box
+
google.maps.event.addListener(map, 'click', function (event) {
- bearing = parseFloat(document.control_panel.bearing.value);
- placeMarker(event.latLng, bearing, false);
+ if (document.control_panel.click_bearing.checked) {
+ 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
}
+ //
+ // Compass Support ----------------------------------------------------------------
+ //
+
+ function startCompass()
+ {
+ window.addEventListener('deviceorientation', onDeviceOrientationChange); // Gyroscope
+ }
+
+ function testForCompass()
+ {
+ var ua = navigator.userAgent.toLowerCase();
+ // Let's do _something_ for unsupported browsers.
+ if (ua.indexOf('firefox') != -1 || ua.indexOf('opera') != -1 || ua.indexOf('msie') != -1)
+ {
+ hasCompass = false;
+ startCompass();
+ }
+ else if (ua.indexOf('iphone os 4') != -1)
+ {
+ hasCompass = false;
+ startCompass();
+ }
+ else if (window.DeviceOrientationEvent)
+ {
+ window.addEventListener('deviceorientation', compassTest);
+ }
+ else
+ {
+ hasCompass = false;
+ startCompass();
+ }
+ }
+
+ function compassTest(event)
+ {
+ window.removeEventListener('deviceorientation', compassTest);
+ if (event.webkitCompassHeading != undefined || event.alpha != null) // Device does have a compass
+ {
+ hasCompass = true;
+ }
+ else
+ {
+ hasCompass = false;
+ }
+ startCompass();
+ }
+
+ // this gets called (a lot) when device orientation changes
+
+ function onDeviceOrientationChange(event) {
+ if (event.webkitCompassHeading != undefined)
+ currentHeading = (360 - event.webkitCompassHeading);
+ else if (event.alpha != null)
+ currentHeading = (270 - event.alpha) * -1;
+ document.control_panel.bearing.value = Math.round(currentHeading);
+ }
+
// remove leading and trailing whitespace from a string
function trim(stringToTrim) {
}
+ //
+ // Helper Funtions ----------------------------------------------------------------
+ //
+
// calculate distance between two points
rad = function(x) {return x*Math.PI/180;}
return d.toFixed(3);
}
+ // 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;
+ }
+
+ 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);
+ }
+ }
+
+
+ //
+ // GUI Call Backs ----------------------------------------------------------------
+ //
+
+ function addBearing2() {
+ if (geolocationSupport) {
+ if (hasCompass) {
+ bearing = currentHeading;
+ }
+ else {
+ bearing = document.control_panel.bearing.value;
+ }
+ var latlng = new google.maps.LatLng(geolat, geolng);
+ placeMarker(latlng, bearing, false);
+ map.setCenter(latlng);
+ }
+ }
+
+ // rec-center map on curent location if scroll/pinch events screw us up
+
+ function whereAmI() {
+ var latlng = new google.maps.LatLng(geolat, geolng);
+ map.setCenter(latlng);
+ }
// sets up map for a new bearings
// adds bearing to bearings[] array but doesn't write to text file
+ function clearDatabase() {
+
+ // call CGI to rm bearings.txt
+
+ downloadUrl("/cgi-bin/cleardb.cgi",function(doc){});
+
+ // delete all existing bearings
+
+ if (bearings) {
+ for (j in bearings) {
+ bearings[j].marker.setMap(null);
+ bearings[j].path.setMap(null);
+ }
+ }
+ bearings = [];
+ }
+
+ function debugClicked() {
+ if (document.control_panel.debug_enable.checked)
+ debug_enable = 1;
+ else
+ debug_enable = 0;
+ }
+
+
+ //
+ // Adding a new Bearing ---------------------------------------------------------
+ //
+
function addNewBearing(lat, lng, bearing, isNewBearing) {
// create the marker at point where bearing starts
path: newPath};
bearings.push(newBearing);
- 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() {
+ google.maps.event.addListener(newMarker, "dblclick", function() {
// remove bearing from bearings array
}
+ //
+ // Cookie Support ----------------------------------------------------------------
+ //
+
// save state (config info) to our cookie
function setCookie() {
}
- // 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) {
}
+ //
+ // Async html request foo ----------------------------------------------------------------
+ //
+
/**
* Returns an XMLHttp instance to use for asynchronous
* downloading. This method will never throw an exception, but will
}
};
- 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){});
-
- // delete all existing bearings
-
- if (bearings) {
- for (j in bearings) {
- bearings[j].marker.setMap(null);
- bearings[j].path.setMap(null);
- }
- }
- bearings = [];
- }
-
- function debugClicked() {
- if (document.control_panel.debug_enable.checked)
- debug_enable = 1;
- else
- debug_enable = 0;
- }
</script>
<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="bottom">
- <div id="control" style="width: 100%;">
- <h3>Contol Panel</h3>
+ <div id="control">
<form name="control_panel">
<table>
<tr>
- <td>Bearing</td>
- <td><input type="text" name="bearing" value="0" size="15"></td>
+ <td><input type="button" value="Add Bearing" onclick="addBearing2()"></td>
+ <td><input type="text" name="bearing" value="0" size="10" ></td>
+ <td><input type="button" value="Where Am I" onclick="whereAmI()"></td>
</tr>
<tr>
<td>Clear Database</td>
- <td width="60"><input type="button" value="Clear" onclick="clearDatabase()"></td>
+ <td><input type="button" value="Clear" onclick="clearDatabase()"></td>
+ </tr>
+ <tr>
+ <td>Click to Add Bearing</td>
+ <td><input type="checkbox" name="click_bearing"></td>
</tr>
<tr>
<td>Debug Messages</td>
<td><input type="checkbox" name="debug_enable" onclick="debugClicked()"></td>
</tr>
+ <tr>
+ <td>Help</td>
+ <td><a href="https://freetel.svn.sourceforge.net/svnroot/freetel/foxy/README.txt">Foxy README</a></td>
+ </tr>
</table>
</form>
+ </div>
- <div id="tests" style="width: 100%;">
+ <div id="tests">
<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%;">
+ <div id="debugging_output">
<h3>Debugging Output</h3>
<ol id="debugging_messages">
</ol>