1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
3 * You can obtain one at http://mozilla.org/MPL/2.0/. */
5 // Get the best-available objects for indexedDB and requestAnimationFrame.
6 window.indexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB || window.msIndexedDB;
7 window.requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame;
10 var gAppInitDone = false;
11 var gUIHideCountdown = 0;
13 var gTrackUpdateInterval;
14 var gAction, gActionLabel;
15 var gOSMAPIURL = "https://api.openstreetmap.org/";
17 oauth_consumer_key: "6jjWwlbhGqyYeCdlFE1lTGG6IRGOv1yKpFxkcq2z",
18 oauth_secret: "A21gUeDM6mdoQgbA9uF7zJ13sbUQrNG7QQ4oSrKA",
19 url: "https://www.openstreetmap.org",
20 landing: "auth-done.html",
23 window.onload = function() {
24 gAction = document.getElementById("action");
25 gActionLabel = document.getElementById("actionlabel");
27 var mSel = document.getElementById("mapSelector");
28 for (var mapStyle in gMapStyles) {
29 var opt = document.createElement("option");
31 opt.text = gMapStyles[mapStyle].name;
35 var areas = document.getElementsByClassName("overlayArea");
36 for (var i = 0; i <= areas.length - 1; i++) {
37 areas[i].addEventListener("mouseup", uiEvHandler, false);
38 areas[i].addEventListener("mousemove", uiEvHandler, false);
39 areas[i].addEventListener("mousedown", uiEvHandler, false);
40 areas[i].addEventListener("mouseout", uiEvHandler, false);
42 areas[i].addEventListener("touchstart", uiEvHandler, false);
43 areas[i].addEventListener("touchmove", uiEvHandler, false);
44 areas[i].addEventListener("touchend", uiEvHandler, false);
45 areas[i].addEventListener("touchcancel", uiEvHandler, false);
46 areas[i].addEventListener("touchleave", uiEvHandler, false);
49 document.getElementById("body").addEventListener("keydown", uiEvHandler, false);
51 if (navigator.platform.length == "") {
52 // For Firefox OS, don't display the "save" button.
53 // Do this by setting the debugHide class for testing in debug mode.
54 document.getElementById("saveTrackButton").classList.add("debugHide");
57 // Without OAuth, the login data is useless
58 //document.getElementById("uploadSettingsArea").classList.remove("debugHide");
59 // As login data is useless for now, always enable upload button
60 document.getElementById("uploadTrackButton").disabled = false;
63 // Note that GPX upload returns an error 500 on the dev API right now.
64 gOSMAPIURL = "http://api06.dev.openstreetmap.org/";
67 gAction.addEventListener("dbinit-done", initMap, false);
68 gAction.addEventListener("mapinit-done", postInit, false);
69 console.log("starting DB init...");
73 function postInit(aEvent) {
74 gAction.removeEventListener(aEvent.type, postInit, false);
75 console.log("init done, draw map.");
76 gMapPrefsLoaded = true;
78 //gMap.resizeAndDraw(); <-- HACK: This triggers bug 1001853, work around with a delay.
79 window.setTimeout(gMap.resizeAndDraw, 100);
80 gActionLabel.textContent = "";
81 gAction.style.display = "none";
82 setTracking(document.getElementById("trackCheckbox"));
83 gPrefs.get(gDebug ? "osm_dev_user" : "osm_user", function(aValue) {
85 document.getElementById("uploadUser").value = aValue;
86 document.getElementById("uploadTrackButton").disabled = false;
89 gPrefs.get(gDebug ? "osm_dev_pwd" : "osm_pwd", function(aValue) {
90 var upwd = document.getElementById("uploadPwd");
92 document.getElementById("uploadPwd").value = aValue;
96 window.onresize = function() {
100 function initDB(aEvent) {
103 gAction.removeEventListener(aEvent.type, initDB, false);
104 var request = window.indexedDB.open("MainDB-lantea", 2);
105 request.onerror = function(event) {
106 // Errors can be handled here. Error codes explain in:
107 // https://developer.mozilla.org/en/IndexedDB/IDBDatabaseException#Constants
109 console.log("error opening mainDB: " + event.target.errorCode);
111 request.onsuccess = function(event) {
112 mainDB = request.result;
113 var throwEv = new CustomEvent("dbinit-done");
114 gAction.dispatchEvent(throwEv);
116 request.onupgradeneeded = function(event) {
117 mainDB = request.result;
118 var ver = mainDB.version || 0; // version is empty string for a new DB
120 console.log("mainDB has version " + ver + ", upgrade needed.");
121 if (!mainDB.objectStoreNames.contains("prefs")) {
122 // Create a "prefs" objectStore.
123 var prefsStore = mainDB.createObjectStore("prefs");
125 if (!mainDB.objectStoreNames.contains("track")) {
126 // Create a "track" objectStore.
127 var trackStore = mainDB.createObjectStore("track", {autoIncrement: true});
129 if (!mainDB.objectStoreNames.contains("tilecache")) {
130 // Create a "tilecache" objectStore.
131 var tilecacheStore = mainDB.createObjectStore("tilecache");
133 mainDB.onversionchange = function(event) {
142 if (gUIHideCountdown <= 0) {
143 var areas = document.getElementsByClassName('overlayArea');
144 for (var i = 0; i <= areas.length - 1; i++) {
145 areas[i].classList.remove("hidden");
147 setTimeout(maybeHideUI, 1000);
149 gUIHideCountdown = 5;
152 function maybeHideUI() {
154 if (gUIHideCountdown <= 0) {
155 var areas = document.getElementsByClassName('overlayArea');
156 for (var i = 0; i <= areas.length - 1; i++) {
157 areas[i].classList.add("hidden");
161 setTimeout(maybeHideUI, 1000);
165 function updateTrackInfo() {
166 document.getElementById("trackLengthNum").textContent = calcTrackLength().toFixed(1);
167 var duration = calcTrackDuration();
168 var durationM = Math.round(duration/60);
169 var durationH = Math.floor(durationM/60); durationM = durationM - durationH * 60;
170 document.getElementById("trackDurationH").style.display = durationH ? "inline" : "none";
171 document.getElementById("trackDurationHNum").textContent = durationH;
172 document.getElementById("trackDurationMNum").textContent = durationM;
175 function toggleTrackArea() {
176 var fs = document.getElementById("trackArea");
177 if (fs.classList.contains("hidden")) {
178 fs.classList.remove("hidden");
180 gTrackUpdateInterval = setInterval(updateTrackInfo, 1000);
183 clearInterval(gTrackUpdateInterval);
184 fs.classList.add("hidden");
188 function toggleSettings() {
189 var fs = document.getElementById("settingsArea");
190 if (fs.classList.contains("hidden")) {
191 fs.classList.remove("hidden");
195 fs.classList.add("hidden");
199 function toggleFullscreen() {
200 if ((document.fullScreenElement && document.fullScreenElement !== null) ||
201 (document.mozFullScreenElement && document.mozFullScreenElement !== null) ||
202 (document.webkitFullScreenElement && document.webkitFullScreenElement !== null)) {
203 if (document.cancelFullScreen) {
204 document.cancelFullScreen();
205 } else if (document.mozCancelFullScreen) {
206 document.mozCancelFullScreen();
207 } else if (document.webkitCancelFullScreen) {
208 document.webkitCancelFullScreen();
212 var elem = document.getElementById("body");
213 if (elem.requestFullScreen) {
214 elem.requestFullScreen();
215 } else if (elem.mozRequestFullScreen) {
216 elem.mozRequestFullScreen();
217 } else if (elem.webkitRequestFullScreen) {
218 elem.webkitRequestFullScreen();
223 function showUploadDialog() {
224 var dia = document.getElementById("dialogArea");
225 var areas = dia.children;
226 for (var i = 0; i <= areas.length - 1; i++) {
227 areas[i].style.display = "none";
229 document.getElementById("uploadDialog").style.display = "block";
230 document.getElementById("uploadTrackButton").disabled = true;
231 dia.classList.remove("hidden");
234 function showGLWarningDialog() {
235 var dia = document.getElementById("dialogArea");
236 var areas = dia.children;
237 for (var i = 0; i <= areas.length - 1; i++) {
238 areas[i].style.display = "none";
240 document.getElementById("noGLwarning").style.display = "block";
241 dia.classList.remove("hidden");
244 function cancelDialog() {
245 document.getElementById("dialogArea").classList.add("hidden");
246 document.getElementById("uploadTrackButton").disabled = false;
250 handleEvent: function(aEvent) {
251 var touchEvent = aEvent.type.indexOf('touch') != -1;
253 switch (aEvent.type) {
267 function setUploadField(aField) {
270 gPrefs.set(gDebug ? "osm_dev_user" : "osm_user", aField.value);
271 document.getElementById("uploadTrackButton").disabled = !aField.value.length;
274 gPrefs.set(gDebug ? "osm_dev_pwd" : "osm_pwd", aField.value);
279 function makeISOString(aTimestamp) {
280 // ISO time format is YYYY-MM-DDTHH:mm:ssZ
281 var tsDate = new Date(aTimestamp);
282 // Note that .getUTCMonth() returns a number between 0 and 11 (0 for January)!
283 return tsDate.getUTCFullYear() + "-" +
284 (tsDate.getUTCMonth() < 9 ? "0" : "") + (tsDate.getUTCMonth() + 1 ) + "-" +
285 (tsDate.getUTCDate() < 10 ? "0" : "") + tsDate.getUTCDate() + "T" +
286 (tsDate.getUTCHours() < 10 ? "0" : "") + tsDate.getUTCHours() + ":" +
287 (tsDate.getUTCMinutes() < 10 ? "0" : "") + tsDate.getUTCMinutes() + ":" +
288 (tsDate.getUTCSeconds() < 10 ? "0" : "") + tsDate.getUTCSeconds() + "Z";
291 function convertTrack(aTargetFormat) {
293 switch (aTargetFormat) {
295 out += '<?xml version="1.0" encoding="UTF-8" ?>' + "\n\n";
296 out += '<gpx version="1.0" creator="Lantea" xmlns="http://www.topografix.com/GPX/1/0">' + "\n";
298 out += ' <trk>' + "\n";
299 out += ' <trkseg>' + "\n";
300 for (var i = 0; i < gTrack.length; i++) {
301 if (gTrack[i].beginSegment && i > 0) {
302 out += ' </trkseg>' + "\n";
303 out += ' <trkseg>' + "\n";
305 out += ' <trkpt lat="' + gTrack[i].coords.latitude + '" lon="' +
306 gTrack[i].coords.longitude + '">' + "\n";
307 if (gTrack[i].coords.altitude) {
308 out += ' <ele>' + gTrack[i].coords.altitude + '</ele>' + "\n";
310 out += ' <time>' + makeISOString(gTrack[i].time) + '</time>' + "\n";
311 out += ' </trkpt>' + "\n";
313 out += ' </trkseg>' + "\n";
314 out += ' </trk>' + "\n";
316 out += '</gpx>' + "\n";
319 out = JSON.stringify(gTrack);
327 function saveTrack() {
329 var outDataURI = "data:application/gpx+xml," +
330 encodeURIComponent(convertTrack("gpx"));
331 window.open(outDataURI, 'GPX Track');
335 function saveTrackDump() {
337 var outDataURI = "data:application/json," +
338 encodeURIComponent(convertTrack("json"));
339 window.open(outDataURI, 'JSON dump');
343 function uploadTrack() {
344 // Hide all areas in the dialog.
345 var dia = document.getElementById("dialogArea");
346 var areas = dia.children;
347 for (var i = 0; i <= areas.length - 1; i++) {
348 areas[i].style.display = "none";
350 // Reset all the fields in the status area.
351 document.getElementById("uploadStatusCloseButton").disabled = true;
352 document.getElementById("uploadInProgress").style.display = "block";
353 document.getElementById("uploadSuccess").style.display = "none";
354 document.getElementById("uploadFailed").style.display = "none";
355 document.getElementById("uploadError").style.display = "none";
356 document.getElementById("uploadErrorMsg").textContent = "";
357 // Now show the status area.
358 document.getElementById("uploadStatus").style.display = "block";
360 // See http://wiki.openstreetmap.org/wiki/Api06#Uploading_traces
361 var trackBlob = new Blob([convertTrack("gpx")],
362 { "type" : "application/gpx+xml" });
363 var formData = new FormData();
364 formData.append("file", trackBlob);
365 var desc = document.getElementById("uploadDesc").value;
366 formData.append("description",
367 desc.length ? desc : "Track recorded via Lantea Maps");
368 //formData.append("tags", "");
369 formData.append("visibility",
370 document.getElementById("uploadVisibility").value);
372 /* GPS trace upload API still only supports HTTP Basic Auth. This below would be OAuth code to try.
373 // Init OSM Auth, see https://github.com/osmlab/osm-auth
375 oauth_consumer_key: gOSMOAuthData.oauth_consumer_key,
376 oauth_secret: gOSMOAuthData.oauth_secret,
377 url: gOSMOAuthData.url,
378 landing: gOSMOAuthData.landing,
379 auto: true // show a login form if the user is not authenticated and
380 // you try to do a call
383 // Do an authenticate request first, so that we actuall do the login.
384 if (!auth.authenticated) {
385 auth.authenticate(function(err, xhrresponse) {
387 reportUploadStatus(false);
390 reportUploadStatus(true);
394 if (!auth.authenticated) {
395 reportUploadStatus(false);
398 // Only now do the actual upload.
401 path: "/api/0.6/gpx/create",
403 options: {"header": {"Content-Type": "multipart/form-data"}},
405 function(err, xhrresponse) {
407 reportUploadStatus(false);
410 reportUploadStatus(true);
416 // Do an empty POST request first, so that we don't send everything,
417 // then ask for credentials, and then send again.
418 var hXHR = new XMLHttpRequest();
419 hXHR.onreadystatechange = function() {
420 if (hXHR.readyState == 4 && (hXHR.status == 200 || hXHR.status == 400)) {
421 // 400 is Bad Request, but that's expected as this was empty.
422 // So far so good, init actual upload.
423 var XHR = new XMLHttpRequest();
424 XHR.onreadystatechange = function() {
425 if (XHR.readyState == 4 && XHR.status == 200) {
426 // Everthing looks fine.
427 reportUploadStatus(true);
428 } else if (XHR.readyState == 4 && XHR.status != 200) {
429 // Fetched the wrong page or network error...
430 reportUploadStatus(false);
433 XHR.open("POST", gOSMAPIURL + "api/0.6/gpx/create", true);
434 // Cross-Origin XHR doesn't allow username/password (HTTP Auth).
435 // So, we'll ask the user for entering credentials with rather ugly UI.
436 XHR.withCredentials = true;
438 XHR.send(formData); // Send actual form data.
441 reportUploadStatus(false, e);
443 } else if (hXHR.readyState == 4 && hXHR.status != 200) {
444 // Fetched the wrong page or network error...
445 reportUploadStatus(false);
448 hXHR.open("POST", gOSMAPIURL + "api/0.6/gpx/create", true);
449 // Cross-Origin XHR doesn't allow username/password (HTTP Auth).
450 // So, we'll ask the user for entering credentials with rather ugly UI.
451 hXHR.withCredentials = true;
453 hXHR.send(); // Empty request, see above.
456 reportUploadStatus(false, e);
460 function reportUploadStatus(aSuccess, aMessage) {
461 document.getElementById("uploadStatusCloseButton").disabled = false;
462 document.getElementById("uploadInProgress").style.display = "none";
464 document.getElementById("uploadSuccess").style.display = "block";
467 document.getElementById("uploadErrorMsg").textContent = aMessage;
468 document.getElementById("uploadError").style.display = "block";
471 document.getElementById("uploadFailed").style.display = "block";
478 get: function(aKey, aCallback) {
481 var transaction = mainDB.transaction([this.objStore]);
482 var request = transaction.objectStore(this.objStore).get(aKey);
483 request.onsuccess = function(event) {
484 aCallback(request.result, event);
486 request.onerror = function(event) {
487 // Errors can be handled here.
488 aCallback(undefined, event);
492 set: function(aKey, aValue, aCallback) {
496 var transaction = mainDB.transaction([this.objStore], "readwrite");
497 var objStore = transaction.objectStore(this.objStore);
498 var request = objStore.put(aValue, aKey);
499 request.onsuccess = function(event) {
502 aCallback(success, event);
504 request.onerror = function(event) {
505 // Errors can be handled here.
507 aCallback(success, event);
511 unset: function(aKey, aCallback) {
515 var transaction = mainDB.transaction([this.objStore], "readwrite");
516 var request = transaction.objectStore(this.objStore).delete(aKey);
517 request.onsuccess = function(event) {
520 aCallback(success, event);
522 request.onerror = function(event) {
523 // Errors can be handled here.
525 aCallback(success, event);
533 getList: function(aCallback) {
536 var transaction = mainDB.transaction([this.objStore]);
537 var objStore = transaction.objectStore(this.objStore);
538 if (objStore.getAll) { // currently Mozilla-specific
539 objStore.getAll().onsuccess = function(event) {
540 aCallback(event.target.result);
543 else { // Use cursor (standard method).
545 objStore.openCursor().onsuccess = function(event) {
546 var cursor = event.target.result;
548 tPoints.push(cursor.value);
558 getListStepped: function(aCallback) {
561 var transaction = mainDB.transaction([this.objStore]);
562 var objStore = transaction.objectStore(this.objStore);
563 // Use cursor in reverse direction (so we get the most recent position first)
564 objStore.openCursor(null, "prev").onsuccess = function(event) {
565 var cursor = event.target.result;
567 aCallback(cursor.value);
576 push: function(aValue, aCallback) {
579 var transaction = mainDB.transaction([this.objStore], "readwrite");
580 var objStore = transaction.objectStore(this.objStore);
581 var request = objStore.add(aValue);
582 request.onsuccess = function(event) {
584 aCallback(request.result, event);
586 request.onerror = function(event) {
587 // Errors can be handled here.
589 aCallback(false, event);
593 clear: function(aCallback) {
597 var transaction = mainDB.transaction([this.objStore], "readwrite");
598 var request = transaction.objectStore(this.objStore).clear();
599 request.onsuccess = function(event) {
602 aCallback(success, event);
604 request.onerror = function(event) {
605 // Errors can be handled here.
607 aCallback(success, event);