+
+function visibilityEvHandler() {
+ // Immediately draw if we just got visible.
+ if (document.hidden != true) {
+ gMap.draw();
+ }
+ // No need to handle the event where we become invisible as we care only draw
+ // when we are visible anyhow.
+}
+
+var geofake = {
+ tracking: false,
+ lastPos: {x: undefined, y: undefined},
+ watchPosition: function(aSuccessCallback, aErrorCallback, aPrefObject) {
+ this.tracking = true;
+ var watchCall = function() {
+ // calc new position in lat/lon degrees
+ // 90° on Earth surface are ~10,000 km at the equator,
+ // so try moving at most 10m at a time
+ if (geofake.lastPos.x)
+ geofake.lastPos.x += (Math.random() - .5) * 90 / 1000000
+ else
+ geofake.lastPos.x = 48.208174
+ if (geofake.lastPos.y)
+ geofake.lastPos.y += (Math.random() - .5) * 90 / 1000000
+ else
+ geofake.lastPos.y = 16.373819
+ aSuccessCallback({timestamp: Date.now(),
+ coords: {latitude: geofake.lastPos.x,
+ longitude: geofake.lastPos.y,
+ accuracy: 20}});
+ if (geofake.tracking)
+ setTimeout(watchCall, 1000);
+ };
+ setTimeout(watchCall, 1000);
+ return "foo";
+ },
+ clearWatch: function(aID) {
+ this.tracking = false;
+ }
+}
+
+function setCentering(aCheckbox) {
+ if (gMapPrefsLoaded && mainDB)
+ gPrefs.set("center_map", aCheckbox.checked);
+ gCenterPosition = aCheckbox.checked;
+}
+
+function setTracking(aCheckbox) {
+ if (gMapPrefsLoaded && mainDB)
+ gPrefs.set("tracking_enabled", aCheckbox.checked);
+ if (aCheckbox.checked)
+ startTracking();
+ else
+ endTracking();
+}
+
+function startTracking() {
+ if (gGeolocation) {
+ gActionLabel.textContent = "Establishing Position";
+ gAction.style.display = "block";
+ if (navigator.requestWakeLock) {
+ gGPSWakeLock = navigator.requestWakeLock("gps");
+ }
+ gGeoWatchID = gGeolocation.watchPosition(
+ function(position) {
+ if (gActionLabel.textContent) {
+ gActionLabel.textContent = "";
+ gAction.style.display = "none";
+ }
+ // Coords spec: https://developer.mozilla.org/en/XPCOM_Interface_Reference/NsIDOMGeoPositionCoords
+ var tPoint = {time: position.timestamp,
+ coords: {latitude: position.coords.latitude,
+ longitude: position.coords.longitude,
+ altitude: position.coords.altitude,
+ accuracy: position.coords.accuracy,
+ altitudeAccuracy: position.coords.altitudeAccuracy,
+ heading: position.coords.heading,
+ speed: position.coords.speed},
+ beginSegment: !gLastTrackPoint};
+ // Only add point to track is accuracy is good enough.
+ if (tPoint.coords.accuracy < gMinTrackAccuracy) {
+ gLastTrackPoint = tPoint;
+ gTrack.push(tPoint);
+ try { gTrackStore.push(tPoint); } catch(e) {}
+ var redrawn = false;
+ if (gCenterPosition) {
+ var posCoord = gps2xy(position.coords.latitude,
+ position.coords.longitude);
+ if (Math.abs(gMap.pos.x - posCoord.x) > gMap.width * gMap.zoomFactor / 4 ||
+ Math.abs(gMap.pos.y - posCoord.y) > gMap.height * gMap.zoomFactor / 4) {
+ gMap.pos.x = posCoord.x;
+ gMap.pos.y = posCoord.y;
+ gMap.draw(); // This draws the current point as well.
+ redrawn = true;
+ }
+ }
+ if (!redrawn)
+ gTrackLayer.undrawCurrentLocation();
+ gTrackLayer.drawTrackPoint(gTrack.length-1);
+ }
+ gTrackLayer.drawCurrentLocation(tPoint);
+ },
+ function(error) {
+ // Ignore erros for the moment, but this is good for debugging.
+ // See https://developer.mozilla.org/en/Using_geolocation#Handling_errors
+ if (gDebug)
+ console.log(error.message);
+ },
+ {enableHighAccuracy: true}
+ );
+ }
+}
+
+function endTracking() {
+ if (gActionLabel.textContent) {
+ gActionLabel.textContent = "";
+ gAction.style.display = "none";
+ }
+ if (navigator.requestWakeLock && gGPSWakeLock) {
+ console.log("releasing WakeLock");
+ gGPSWakeLock.unlock();
+ }
+ if (gGeoWatchID) {
+ gGeolocation.clearWatch(gGeoWatchID);
+ }
+}
+
+function clearTrack() {
+ gTrack = [];
+ gTrackStore.clear();
+ gTrackLayer.drawTrack();
+}
+
+function loadTrackFromBackend(aTrackId, aFeedbackElement, aSuccessCallback) {
+ if (aFeedbackElement) {
+ aFeedbackElement.textContent = "Loading...";
+ // If someone loads without pointing to UI (e.g. for debugging), we just overwrite whatever's loaded.
+ if (calcTrackLength() > 1) {
+ aFeedbackElement.textContent = "Track >1km loaded, please save/clear.";
+ aFeedbackElement.classList.add("error");
+ return;
+ }
+ }
+ fetchBackend("track_json?id=" + encodeURIComponent(aTrackId), "GET", null,
+ function(aResult, aStatusCode) {
+ if (aStatusCode >= 400) {
+ console.log("loading track failed: " + aStatusCode + ", result: " + aResult.message);
+ if (aFeedbackElement) {
+ aFeedbackElement.textContent = "Error: " + aResult;
+ aFeedbackElement.classList.add("error");
+ }
+ }
+ else if (aResult) {
+ loadTrack(aResult);
+ if (aSuccessCallback) {
+ aSuccessCallback();
+ }
+ }
+ else { // If no result is returned, we assume a general error.
+ console.log("Error getting track with ID " + aTrackId + " from backend.");
+ if (aFeedbackElement) {
+ aFeedbackElement.textContent = "Error fetching track from backend.";
+ aFeedbackElement.classList.add("error");
+ }
+ }
+ }
+ );
+}
+
+function loadTrack(aTrack) {
+ console.log("Loading track with " + aTrack.length + " points.");
+ gTrack = aTrack;
+ gTrackStore.clear(function(aSuccess, aEvent) {
+ for (var i = 0; i < gTrack.length; i++) {
+ try { gTrackStore.push(gTrack[i]); } catch(e) {}
+ }
+ });
+ gTrackLayer.drawTrack(); // Draws from gTrack, so not problem that gTrackStore is filled async.
+}
+
+var gTileService = {
+ objStore: "tilecache",
+
+ ageLimit: 14 * 86400 * 1000, // 2 weeks (in ms)
+
+ get: function(aStyle, aCoords, aCallback) {
+ var norm = normalizeCoords(aCoords);
+ var dbkey = getTileKey(aStyle, norm);
+ this.getDBCache(dbkey, function(aResult, aEvent) {
+ if (aResult) {
+ // We did get a cached object.
+ aCallback(aResult.image, aStyle, aCoords, dbkey);
+ // Look at the timestamp and return if it's not too old.
+ if (aResult.timestamp + gTileService.ageLimit > Date.now())
+ return;
+ // Reload cached tile otherwise.
+ var oldDate = new Date(aResult.timestamp);
+ console.log("reload cached tile: " + dbkey + " - " + oldDate.toUTCString());
+ }
+ // Retrieve image from the web and store it in the cache.
+ var XHR = new XMLHttpRequest();
+ XHR.open("GET",
+ gMapStyles[aStyle].url
+ .replace("{x}", norm.x)
+ .replace("{y}", norm.y)
+ .replace("{z}", norm.z)
+ .replace("[a-c]", String.fromCharCode(97 + Math.floor(Math.random() * 3)))
+ .replace("[a-d]", String.fromCharCode(97 + Math.floor(Math.random() * 4)))
+ .replace("[1-4]", 1 + Math.floor(Math.random() * 4)),
+ true);
+ XHR.responseType = "blob";
+ XHR.addEventListener("load", function () {
+ if (XHR.status === 200) {
+ var blob = XHR.response;
+ aCallback(blob, aStyle, aCoords, dbkey);
+ gTileService.setDBCache(dbkey, {image: blob, timestamp: Date.now()});
+ }
+ }, false);
+ XHR.send();
+ });
+ },
+
+ getDBCache: function(aKey, aCallback) {
+ if (!mainDB)
+ return;
+ var transaction = mainDB.transaction([this.objStore]);
+ var request = transaction.objectStore(this.objStore).get(aKey);
+ request.onsuccess = function(event) {
+ aCallback(request.result, event);
+ };
+ request.onerror = function(event) {
+ // Errors can be handled here.
+ aCallback(undefined, event);
+ };
+ },
+
+ setDBCache: function(aKey, aValue, aCallback) {
+ if (!mainDB)
+ return;
+ var success = false;
+ var transaction = mainDB.transaction([this.objStore], "readwrite");
+ var objStore = transaction.objectStore(this.objStore);
+ var request = objStore.put(aValue, aKey);
+ request.onsuccess = function(event) {
+ success = true;
+ if (aCallback)
+ aCallback(success, event);
+ };
+ request.onerror = function(event) {
+ // Errors can be handled here.
+ if (aCallback)
+ aCallback(success, event);
+ };
+ },
+
+ unsetDBCache: function(aKey, aCallback) {
+ if (!mainDB)
+ return;
+ var success = false;
+ var transaction = mainDB.transaction([this.objStore], "readwrite");
+ var request = transaction.objectStore(this.objStore).delete(aKey);
+ request.onsuccess = function(event) {
+ success = true;
+ if (aCallback)
+ aCallback(success, event);
+ };
+ request.onerror = function(event) {
+ // Errors can be handled here.
+ if (aCallback)
+ aCallback(success, event);
+ }
+ },
+
+ clearDB: function(aCallback) {
+ if (!mainDB)
+ return;
+ var success = false;
+ var transaction = mainDB.transaction([this.objStore], "readwrite");
+ var request = transaction.objectStore(this.objStore).clear();
+ request.onsuccess = function(event) {
+ success = true;
+ console.log("Cache cleared.");
+ if (aCallback)
+ aCallback(success, event);
+ };
+ request.onerror = function(event) {
+ // Errors can be handled here.
+ console.log("Error clearing cache!");
+ if (aCallback)
+ aCallback(success, event);
+ }
+ }
+};