add first pieces of support for webGL maps
[lantea.git] / js / map.js
CommitLineData
a7393a71
RK
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/. */
23cd2dcc 4
ecde0af2 5var gMapCanvas, gMapContext, gGLMapCanvas, gMapGL, gTrackCanvas, gTrackContext, gGeolocation;
0dc9cd0d 6var gDebug = false;
23cd2dcc
RK
7
8var gTileSize = 256;
9var gMaxZoom = 18; // The minimum is 0.
10
b054bd48
RK
11var gMinTrackAccuracy = 1000; // meters
12var gTrackWidth = 2; // pixels
13var gTrackColor = "#FF0000";
4b1d0915
RK
14var gCurLocSize = 6; // pixels
15var gCurLocColor = "#A00000";
b054bd48 16
b47b4a65
RK
17var gMapStyles = {
18 // OSM tile usage policy: http://wiki.openstreetmap.org/wiki/Tile_usage_policy
55c4a0b7 19 // Find some more OSM ones at http://wiki.openstreetmap.org/wiki/Slippy_map_tilenames#Tile_servers
b47b4a65
RK
20 osm_mapnik:
21 {name: "OpenStreetMap (Mapnik)",
22 url: "http://tile.openstreetmap.org/{z}/{x}/{y}.png",
5a19ec68 23 copyright: 'Map data and imagery &copy; <a href="http://www.openstreetmap.org/">OpenStreetMap</a> contributors, <a href="http://www.openstreetmap.org/copyright">ODbL/CC-BY-SA</a>'},
14e6d3ad
RK
24 osm_cyclemap:
25 {name: "Cycle Map (OSM)",
26 url: "http://[a-c].tile.opencyclemap.org/cycle/{z}/{x}/{y}.png",
5a19ec68 27 copyright: 'Map data and imagery &copy; <a href="http://www.openstreetmap.org/">OpenStreetMap</a> contributors, <a href="http://www.openstreetmap.org/copyright">ODbL/CC-BY-SA</a>'},
14e6d3ad
RK
28 osm_transmap:
29 {name: "Transport Map (OSM)",
30 url: "http://[a-c].tile2.opencyclemap.org/transport/{z}/{x}/{y}.png",
5a19ec68 31 copyright: 'Map data and imagery &copy; <a href="http://www.openstreetmap.org/">OpenStreetMap</a> contributors, <a href="http://www.openstreetmap.org/copyright">ODbL/CC-BY-SA</a>'},
b47b4a65 32 mapquest_open:
55c4a0b7 33 {name: "MapQuest OSM",
5a19ec68
RK
34 url: "http://otile[1-4].mqcdn.com/tiles/1.0.0/osm/{z}/{x}/{y}.png",
35 copyright: 'Map data &copy; <a href="http://www.openstreetmap.org/">OpenStreetMap</a> and contributors (<a href="http://www.openstreetmap.org/copyright">ODbL/CC-BY-SA</a>), tiles Courtesy of <a href="http://www.mapquest.com/">MapQuest</a>.'},
55c4a0b7
RK
36 mapquest_aerial:
37 {name: "MapQuest Open Aerial",
68afcd96 38 url: "http://otile[1-4].mqcdn.com/tiles/1.0.0/sat/{z}/{x}/{y}.jpg",
5a19ec68
RK
39 copyright: 'Tiles Courtesy of <a href="http://www.mapquest.com/">MapQuest</a>, portions Courtesy NASA/JPL-Caltech and U.S. Depart. of Agriculture, Farm Service Agency.'},
40 opengeoserver_arial:
41 {name: "OpenGeoServer Aerial",
42 url: "http://services.opengeoserver.org/tiles/1.0.0/globe.aerial_EPSG3857/{z}/{x}/{y}.png?origin=nw",
43 copyright: 'Tiles by <a href="http://www.opengeoserver.org/">OpenGeoServer.org</a>, <a href="https://creativecommons.org/licenses/by/3.0/at/">CC-BY 3.0 AT</a>.'},
55c4a0b7
RK
44 google_map:
45 {name: "Google Maps",
46 url: " http://mt1.google.com/vt/x={x}&y={y}&z={z}",
47 copyright: 'Map data and imagery &copy; <a href="http://maps.google.com/">Google</a>'},
b47b4a65
RK
48};
49var gActiveMap = "osm_mapnik";
23cd2dcc
RK
50
51var gPos = {x: 35630000.0, // Current position in the map in pixels at the maximum zoom level (18)
52 y: 23670000.0, // The range is 0-67108864 (2^gMaxZoom * gTileSize)
b395419b 53 z: 5}; // This could be fractional if supported being between zoom levels.
23cd2dcc
RK
54
55var gLastMouseX = 0;
56var gLastMouseY = 0;
b395419b 57var gZoomFactor;
23cd2dcc 58
b395419b 59var gLoadingTile;
23cd2dcc 60
3610c22d
RK
61var gMapPrefsLoaded = false;
62
23cd2dcc 63var gDragging = false;
517c0099 64var gDragTouchID, gPinchStartWidth;
23cd2dcc 65
55c4a0b7
RK
66var gGeoWatchID;
67var gTrack = [];
14e6d3ad 68var gLastTrackPoint, gLastDrawnPoint;
05c21757 69var gCenterPosition = true;
55c4a0b7 70
b054bd48
RK
71var gCurPosMapCache;
72
b47b4a65 73function initMap() {
4b12da3a 74 gGeolocation = navigator.geolocation;
ecde0af2 75 // Set up canvas contexts. TODO: Remove 2D map once GL support works.
4b1d0915
RK
76 gMapCanvas = document.getElementById("map");
77 gMapContext = gMapCanvas.getContext("2d");
ecde0af2
RK
78 gGLMapCanvas = document.getElementById("glmap");
79 try {
80 // Try to grab the standard context. If it fails, fallback to experimental.
81 // We also try to tell it we do not need a depth buffer.
82 gMapGL = gGLMapCanvas.getContext("webgl", {depth: false}) ||
83 gGLMapCanvas.getContext("experimental-webgl", {depth: false});
84 gMapGL.viewport(0, 0, gMapGL.drawingBufferWidth, gMapGL.drawingBufferHeight);
85 }
86 catch(e) {}
87 // If we don't have a GL context, give up now
88 if (!gMapGL) {
89 showGLWarningDialog();
90 gMapGL = null;
91 }
4b1d0915
RK
92 gTrackCanvas = document.getElementById("track");
93 gTrackContext = gTrackCanvas.getContext("2d");
b47b4a65
RK
94 if (!gActiveMap)
95 gActiveMap = "osm_mapnik";
23cd2dcc 96
4b12da3a
RK
97 //gDebug = true;
98 if (gDebug) {
99 gGeolocation = geofake;
100 var hiddenList = document.getElementsByClassName("debugHide");
101 // last to first - list of elements with that class is changing!
102 for (var i = hiddenList.length - 1; i >= 0; i--) {
103 hiddenList[i].classList.remove("debugHide");
104 }
105 }
106
ecde0af2
RK
107 gAction.addEventListener("prefload-done", initGL, false);
108
582d50fc
RK
109 console.log("map vars set, loading prefs...");
110 loadPrefs();
111}
b395419b 112
582d50fc
RK
113function loadPrefs(aEvent) {
114 if (aEvent && aEvent.type == "prefs-step") {
115 console.log("wait: " + gWaitCounter);
116 if (gWaitCounter == 0) {
117 gAction.removeEventListener(aEvent.type, loadPrefs, false);
118 gMapPrefsLoaded = true;
119 console.log("prefs loaded.");
120
121 gTrackCanvas.addEventListener("mouseup", mapEvHandler, false);
122 gTrackCanvas.addEventListener("mousemove", mapEvHandler, false);
123 gTrackCanvas.addEventListener("mousedown", mapEvHandler, false);
124 gTrackCanvas.addEventListener("mouseout", mapEvHandler, false);
125
126 gTrackCanvas.addEventListener("touchstart", mapEvHandler, false);
127 gTrackCanvas.addEventListener("touchmove", mapEvHandler, false);
128 gTrackCanvas.addEventListener("touchend", mapEvHandler, false);
129 gTrackCanvas.addEventListener("touchcancel", mapEvHandler, false);
130 gTrackCanvas.addEventListener("touchleave", mapEvHandler, false);
131
132 gTrackCanvas.addEventListener("wheel", mapEvHandler, false);
133
134 document.getElementById("body").addEventListener("keydown", mapEvHandler, false);
135
136 document.getElementById("copyright").innerHTML =
137 gMapStyles[gActiveMap].copyright;
138
139 gLoadingTile = new Image();
140 gLoadingTile.src = "style/loading.png";
141 gLoadingTile.onload = function() {
ecde0af2 142 var throwEv = new CustomEvent("prefload-done");
582d50fc
RK
143 gAction.dispatchEvent(throwEv);
144 };
145 }
146 }
147 else {
148 if (aEvent)
149 gAction.removeEventListener(aEvent.type, loadPrefs, false);
150 gAction.addEventListener("prefs-step", loadPrefs, false);
151 gWaitCounter++;
152 gPrefs.get("position", function(aValue) {
153 if (aValue) {
154 gPos = aValue;
582d50fc 155 }
df81068a
RK
156 gWaitCounter--;
157 var throwEv = new CustomEvent("prefs-step");
158 gAction.dispatchEvent(throwEv);
582d50fc
RK
159 });
160 gWaitCounter++;
161 gPrefs.get("center_map", function(aValue) {
162 if (aValue === undefined)
163 document.getElementById("centerCheckbox").checked = true;
164 else
165 document.getElementById("centerCheckbox").checked = aValue;
166 setCentering(document.getElementById("centerCheckbox"));
167 gWaitCounter--;
168 var throwEv = new CustomEvent("prefs-step");
169 gAction.dispatchEvent(throwEv);
170 });
171 gWaitCounter++;
172 gPrefs.get("tracking_enabled", function(aValue) {
173 if (aValue === undefined)
174 document.getElementById("trackCheckbox").checked = true;
175 else
176 document.getElementById("trackCheckbox").checked = aValue;
177 gWaitCounter--;
178 var throwEv = new CustomEvent("prefs-step");
179 gAction.dispatchEvent(throwEv);
180 });
181 gWaitCounter++;
cef88e12
RK
182 var trackLoadStarted = false;
183 var redrawBase = 100;
6ddefbf9
RK
184 gTrackStore.getListStepped(function(aTPoint) {
185 if (aTPoint) {
186 // Add in front and return new length.
187 var tracklen = gTrack.unshift(aTPoint);
fdaf08db
RK
188 // Redraw track periodically, larger distance the longer it gets.
189 // Initial paint will do initial track drawing.
190 if (tracklen % redrawBase == 0) {
6ddefbf9 191 drawTrack();
fdaf08db
RK
192 redrawBase = tracklen;
193 }
6ddefbf9
RK
194 }
195 else {
196 // Last point received.
197 drawTrack();
198 }
199 if (!trackLoadStarted) {
200 // We have the most recent point, if present, rest will load async.
201 trackLoadStarted = true;
202 gWaitCounter--;
203 var throwEv = new CustomEvent("prefs-step");
204 gAction.dispatchEvent(throwEv);
582d50fc 205 }
582d50fc
RK
206 });
207 }
23cd2dcc
RK
208}
209
ecde0af2
RK
210function initGL() {
211 if (gMapGL) {
212 gMapGL.clearColor(0.0, 0.0, 0.0, 0.5); // Set clear color to black, fully opaque.
213 gMapGL.clear(gMapGL.COLOR_BUFFER_BIT|gMapGL.DEPTH_BUFFER_BIT); // Clear the color.
214
215 // Create and initialize the shaders.
216 var vertShader = gMapGL.createShader(gMapGL.VERTEX_SHADER);
217 var vertShaderSource =
218 'attribute vec2 aVertexPosition;\n' +
219 'attribute vec2 aTextureCoord;\n\n' +
220 'uniform vec2 uResolution;\n\n' +
221 'varying highp vec2 vTextureCoord;\n\n' +
222 'void main(void) {\n' +
223 // convert the rectangle from pixels to -1.0 to +1.0 (clipspace) 0.0 to 1.0
224 ' vec2 clipSpace = aVertexPosition * 2.0 / uResolution - 1.0;\n' +
225 ' gl_Position = vec4(clipSpace * vec2(1, -1), 0, 1);\n' +
226 ' vTextureCoord = aTextureCoord;\n' +
227 '}';
228 var fragShader = gMapGL.createShader(gMapGL.FRAGMENT_SHADER);
229 var fragShaderSource =
230 'varying highp vec2 vTextureCoord;\n\n' +
231 'uniform sampler2D uImage;\n\n' +
232 'void main(void) {\n' +
233 ' gl_FragColor = texture2D(uImage, vTextureCoord);\n' +
234 '}';
235
236 gMapGL.shaderSource(vertShader, vertShaderSource);
237 // Compile the shader program.
238 gMapGL.compileShader(vertShader);
239 // See if it compiled successfully.
240 if (!gMapGL.getShaderParameter(vertShader, gMapGL.COMPILE_STATUS)) {
241 console.log("An error occurred compiling the vertix shader: " + gMapGL.getShaderInfoLog(vertShader));
242 return null;
243 }
244 gMapGL.shaderSource(fragShader, fragShaderSource);
245 // Compile the shader program.
246 gMapGL.compileShader(fragShader);
247 // See if it compiled successfully.
248 if (!gMapGL.getShaderParameter(fragShader, gMapGL.COMPILE_STATUS)) {
249 console.log("An error occurred compiling the fragment shader: " + gMapGL.getShaderInfoLog(fragShader));
250 return null;
251 }
252
253 var shaderProgram = gMapGL.createProgram();
254 gMapGL.attachShader(shaderProgram, vertShader);
255 gMapGL.attachShader(shaderProgram, fragShader);
256 gMapGL.linkProgram(shaderProgram);
257 // If creating the shader program failed, alert
258 if (!gMapGL.getProgramParameter(shaderProgram, gMapGL.LINK_STATUS)) {
259 alert("Unable to initialize the shader program.");
260 }
261 gMapGL.useProgram(shaderProgram);
262 var vertexPositionAttribute = gMapGL.getAttribLocation(shaderProgram, "aVertexPosition");
263 var textureCoordAttribute = gMapGL.getAttribLocation(shaderProgram, "aTextureCoord");
264 var resolutionAttribute = gMapGL.getUniformLocation(shaderProgram, "uResolution");
265
266 var tileVerticesBuffer = gMapGL.createBuffer();
267 gMapGL.bindBuffer(gMapGL.ARRAY_BUFFER, tileVerticesBuffer);
268 // The vertices are the coordinates of the corner points of the square.
269 var vertices = [
270 0.0, 0.0,
271 1.0, 0.0,
272 0.0, 1.0,
273 0.0, 1.0,
274 1.0, 0.0,
275 1.0, 1.0];
276 gMapGL.bufferData(gMapGL.ARRAY_BUFFER, new Float32Array(vertices), gMapGL.STATIC_DRAW);
277 gMapGL.enableVertexAttribArray(textureCoordAttribute);
278 gMapGL.vertexAttribPointer(textureCoordAttribute, 2, gMapGL.FLOAT, false, 0, 0);
279
280 // Map Texture
281 var mapTexture = gMapGL.createTexture();
282 gMapGL.bindTexture(gMapGL.TEXTURE_2D, mapTexture);
283 // Set the parameters so we can render any size image.
284 gMapGL.texParameteri(gMapGL.TEXTURE_2D, gMapGL.TEXTURE_WRAP_S, gMapGL.CLAMP_TO_EDGE);
285 gMapGL.texParameteri(gMapGL.TEXTURE_2D, gMapGL.TEXTURE_WRAP_T, gMapGL.CLAMP_TO_EDGE);
286 gMapGL.texParameteri(gMapGL.TEXTURE_2D, gMapGL.TEXTURE_MIN_FILTER, gMapGL.NEAREST);
287 gMapGL.texParameteri(gMapGL.TEXTURE_2D, gMapGL.TEXTURE_MAG_FILTER, gMapGL.NEAREST);
288 // Upload the image into the texture.
289 gMapGL.texImage2D(gMapGL.TEXTURE_2D, 0, gMapGL.RGBA, gMapGL.RGBA, gMapGL.UNSIGNED_BYTE, gLoadingTile);
290
291 gMapGL.uniform2f(resolutionAttribute, gGLMapCanvas.width, gGLMapCanvas.height);
292
293 // Create a buffer for the position of the rectangle corners.
294 var mapVerticesTextureCoordBuffer = gMapGL.createBuffer();
295 gMapGL.bindBuffer(gMapGL.ARRAY_BUFFER, mapVerticesTextureCoordBuffer);
296 var x_start = 10;
297 var i_width = 512;
298 var y_start = 10;
299 var i_height = 512;
300 var textureCoordinates = [
301 x_start, y_start,
302 x_start + i_width, y_start,
303 x_start, y_start + i_height,
304 x_start, y_start + i_height,
305 x_start + i_width, y_start,
306 x_start + i_width, y_start + i_height];
307 gMapGL.bufferData(gMapGL.ARRAY_BUFFER, new Float32Array(textureCoordinates), gMapGL.STATIC_DRAW);
308 gMapGL.enableVertexAttribArray(vertexPositionAttribute);
309 gMapGL.vertexAttribPointer(vertexPositionAttribute, 2, gMapGL.FLOAT, false, 0, 0);
310
311 // There are 6 indices in textureCoordinates.
312 gMapGL.drawArrays(gMapGL.TRIANGLES, 0, 6);
313 }
314
315 var throwEv = new CustomEvent("mapinit-done");
316 gAction.dispatchEvent(throwEv);
317}
318
23cd2dcc 319function resizeAndDraw() {
321359cd
RK
320 var viewportWidth = Math.min(window.innerWidth, window.outerWidth);
321 var viewportHeight = Math.min(window.innerHeight, window.outerHeight);
ecde0af2 322 if (gMapCanvas && gGLMapCanvas && gTrackCanvas) {
915d4271
RK
323 gMapCanvas.width = viewportWidth;
324 gMapCanvas.height = viewportHeight;
ecde0af2
RK
325 gGLMapCanvas.width = viewportWidth;
326 gGLMapCanvas.height = viewportHeight;
327 gMapGL.viewport(0, 0, gMapGL.drawingBufferWidth, gMapGL.drawingBufferHeight);
915d4271
RK
328 gTrackCanvas.width = viewportWidth;
329 gTrackCanvas.height = viewportHeight;
330 drawMap();
331 showUI();
332 }
23cd2dcc
RK
333}
334
b5e49b95
RK
335// Using scale(x, y) together with drawing old data on scaled canvas would be an improvement for zooming.
336// See https://developer.mozilla.org/en-US/docs/Canvas_tutorial/Transformations#Scaling
337
23cd2dcc
RK
338function zoomIn() {
339 if (gPos.z < gMaxZoom) {
340 gPos.z++;
341 drawMap();
342 }
343}
344
345function zoomOut() {
346 if (gPos.z > 0) {
347 gPos.z--;
348 drawMap();
349 }
350}
351
1222624d
RK
352function zoomTo(aTargetLevel) {
353 aTargetLevel = parseInt(aTargetLevel);
354 if (aTargetLevel >= 0 && aTargetLevel <= gMaxZoom) {
355 gPos.z = aTargetLevel;
356 drawMap();
357 }
358}
359
55c4a0b7
RK
360function gps2xy(aLatitude, aLongitude) {
361 var maxZoomFactor = Math.pow(2, gMaxZoom) * gTileSize;
362 var convLat = aLatitude * Math.PI / 180;
363 var rawY = (1 - Math.log(Math.tan(convLat) +
364 1 / Math.cos(convLat)) / Math.PI) / 2 * maxZoomFactor;
365 var rawX = (aLongitude + 180) / 360 * maxZoomFactor;
366 return {x: Math.round(rawX),
367 y: Math.round(rawY)};
368}
369
370function xy2gps(aX, aY) {
371 var maxZoomFactor = Math.pow(2, gMaxZoom) * gTileSize;
372 var n = Math.PI - 2 * Math.PI * aY / maxZoomFactor;
373 return {latitude: 180 / Math.PI *
374 Math.atan(0.5 * (Math.exp(n) - Math.exp(-n))),
375 longitude: aX / maxZoomFactor * 360 - 180};
376}
377
b47b4a65
RK
378function setMapStyle() {
379 var mapSel = document.getElementById("mapSelector");
380 if (mapSel.selectedIndex >= 0 && gActiveMap != mapSel.value) {
381 gActiveMap = mapSel.value;
95f49ba7
RK
382 document.getElementById("copyright").innerHTML =
383 gMapStyles[gActiveMap].copyright;
b5c85133 384 showUI();
b47b4a65
RK
385 drawMap();
386 }
387}
388
23cd2dcc
RK
389// A sane mod function that works for negative numbers.
390// Returns a % b.
391function mod(a, b) {
392 return ((a % b) + b) % b;
393}
394
a8634d37
RK
395function normalizeCoords(aCoords) {
396 var zoomFactor = Math.pow(2, aCoords.z);
397 return {x: mod(aCoords.x, zoomFactor),
398 y: mod(aCoords.y, zoomFactor),
399 z: aCoords.z};
23cd2dcc
RK
400}
401
402// Returns true if the tile is outside the current view.
403function isOutsideWindow(t) {
404 var pos = decodeIndex(t);
23cd2dcc 405
4b1d0915
RK
406 var zoomFactor = Math.pow(2, gMaxZoom - pos.z);
407 var wid = gMapCanvas.width * zoomFactor;
408 var ht = gMapCanvas.height * zoomFactor;
23cd2dcc 409
4b1d0915
RK
410 pos.x *= zoomFactor;
411 pos.y *= zoomFactor;
23cd2dcc 412
b395419b 413 var sz = gTileSize * zoomFactor;
4b1d0915
RK
414 if (pos.x > gPos.x + wid / 2 || pos.y > gPos.y + ht / 2 ||
415 pos.x + sz < gPos.x - wid / 2 || pos.y - sz < gPos.y - ht / 2)
23cd2dcc
RK
416 return true;
417 return false;
418}
419
420function encodeIndex(x, y, z) {
a8634d37 421 var norm = normalizeCoords({x: x, y: y, z: z});
23cd2dcc
RK
422 return norm.x + "," + norm.y + "," + norm.z;
423}
424
425function decodeIndex(encodedIdx) {
4b1d0915
RK
426 var ind = encodedIdx.split(",", 3);
427 return {x: ind[0], y: ind[1], z: ind[2]};
23cd2dcc
RK
428}
429
b5e49b95
RK
430function drawMap(aPixels, aOverdraw) {
431 // aPixels is an object with left/right/top/bottom members telling how many
432 // pixels on the borders should actually be drawn.
433 // aOverdraw is a bool that tells if we should draw placeholders or draw
434 // straight over the existing content.
435 if (!aPixels)
436 aPixels = {left: gMapCanvas.width, right: gMapCanvas.width,
437 top: gMapCanvas.height, bottom: gMapCanvas.height};
438 if (!aOverdraw)
439 aOverdraw = false;
440
b395419b
RK
441 document.getElementById("zoomLevel").textContent = gPos.z;
442 gZoomFactor = Math.pow(2, gMaxZoom - gPos.z);
4b1d0915
RK
443 var wid = gMapCanvas.width * gZoomFactor; // Width in level 18 pixels.
444 var ht = gMapCanvas.height * gZoomFactor; // Height in level 18 pixels.
b395419b 445 var size = gTileSize * gZoomFactor; // Tile size in level 18 pixels.
23cd2dcc
RK
446
447 var xMin = gPos.x - wid / 2; // Corners of the window in level 18 pixels.
448 var yMin = gPos.y - ht / 2;
449 var xMax = gPos.x + wid / 2;
450 var yMax = gPos.y + ht / 2;
451
3610c22d
RK
452 if (gMapPrefsLoaded && mainDB)
453 gPrefs.set("position", gPos);
454
b5e49b95
RK
455 var tiles = {left: Math.ceil((xMin + aPixels.left * gZoomFactor) / size) -
456 (aPixels.left ? 0 : 1),
457 right: Math.floor((xMax - aPixels.right * gZoomFactor) / size) -
458 (aPixels.right ? 1 : 0),
459 top: Math.ceil((yMin + aPixels.top * gZoomFactor) / size) -
460 (aPixels.top ? 0 : 1),
461 bottom: Math.floor((yMax - aPixels.bottom * gZoomFactor) / size) -
462 (aPixels.bottom ? 1 : 0)};
463
464 // Go through all the tiles in the map, find out if to draw them and do so.
b47b4a65 465 for (var x = Math.floor(xMin / size); x < Math.ceil(xMax / size); x++) {
0118cbd3 466 for (var y = Math.floor(yMin / size); y < Math.ceil(yMax / size); y++) { // slow script warnings on the tablet appear here!
b5e49b95 467 // Only go to the drawing step if we need to draw this tile.
68afcd96
RK
468 if (x < tiles.left || x > tiles.right ||
469 y < tiles.top || y > tiles.bottom) {
b5e49b95
RK
470 // Round here is **CRUCIAL** otherwise the images are filtered
471 // and the performance sucks (more than expected).
472 var xoff = Math.round((x * size - xMin) / gZoomFactor);
473 var yoff = Math.round((y * size - yMin) / gZoomFactor);
474 // Draw placeholder tile unless we overdraw.
68afcd96
RK
475 if (!aOverdraw &&
476 (x < tiles.left -1 || x > tiles.right + 1 ||
477 y < tiles.top -1 || y > tiles.bottom + 1))
b5e49b95
RK
478 gMapContext.drawImage(gLoadingTile, xoff, yoff);
479
480 // Initiate loading/drawing of the actual tile.
481 gTileService.get(gActiveMap, {x: x, y: y, z: gPos.z},
482 function(aImage, aStyle, aCoords) {
483 // Only draw if this applies for the current view.
484 if ((aStyle == gActiveMap) && (aCoords.z == gPos.z)) {
485 var ixMin = gPos.x - wid / 2;
486 var iyMin = gPos.y - ht / 2;
487 var ixoff = Math.round((aCoords.x * size - ixMin) / gZoomFactor);
488 var iyoff = Math.round((aCoords.y * size - iyMin) / gZoomFactor);
489 var URL = window.URL;
490 var imgURL = URL.createObjectURL(aImage);
491 var imgObj = new Image();
492 imgObj.src = imgURL;
493 imgObj.onload = function() {
494 gMapContext.drawImage(imgObj, ixoff, iyoff);
495 URL.revokeObjectURL(imgURL);
496 }
a2131f63 497 }
b5e49b95
RK
498 });
499 }
23cd2dcc
RK
500 }
501 }
6ddefbf9
RK
502 drawTrack();
503}
504
505function drawTrack() {
4b1d0915
RK
506 gLastDrawnPoint = null;
507 gCurPosMapCache = undefined;
508 gTrackContext.clearRect(0, 0, gTrackCanvas.width, gTrackCanvas.height);
14e6d3ad 509 if (gTrack.length) {
55c4a0b7 510 for (var i = 0; i < gTrack.length; i++) {
14e6d3ad
RK
511 drawTrackPoint(gTrack[i].coords.latitude, gTrack[i].coords.longitude,
512 (i + 1 >= gTrack.length));
55c4a0b7 513 }
14e6d3ad 514 }
55c4a0b7
RK
515}
516
14e6d3ad 517function drawTrackPoint(aLatitude, aLongitude, lastPoint) {
55c4a0b7 518 var trackpoint = gps2xy(aLatitude, aLongitude);
14e6d3ad
RK
519 // lastPoint is for optimizing (not actually executing the draw until the last)
520 trackpoint.optimized = (lastPoint === false);
4b1d0915
RK
521 var mappos = {x: Math.round((trackpoint.x - gPos.x) / gZoomFactor + gMapCanvas.width / 2),
522 y: Math.round((trackpoint.y - gPos.y) / gZoomFactor + gMapCanvas.height / 2)};
14e6d3ad
RK
523
524 if (!gLastDrawnPoint || !gLastDrawnPoint.optimized) {
4b1d0915
RK
525 gTrackContext.strokeStyle = gTrackColor;
526 gTrackContext.fillStyle = gTrackContext.strokeStyle;
527 gTrackContext.lineWidth = gTrackWidth;
528 gTrackContext.lineCap = "round";
529 gTrackContext.lineJoin = "round";
14e6d3ad
RK
530 }
531 if (!gLastDrawnPoint || gLastDrawnPoint == trackpoint) {
532 // This breaks optimiziation, so make sure to close path and reset optimization.
533 if (gLastDrawnPoint && gLastDrawnPoint.optimized)
4b1d0915
RK
534 gTrackContext.stroke();
535 gTrackContext.beginPath();
14e6d3ad 536 trackpoint.optimized = false;
4b1d0915
RK
537 gTrackContext.arc(mappos.x, mappos.y,
538 gTrackContext.lineWidth, 0, Math.PI * 2, false);
539 gTrackContext.fill();
55c4a0b7
RK
540 }
541 else {
14e6d3ad 542 if (!gLastDrawnPoint || !gLastDrawnPoint.optimized) {
4b1d0915
RK
543 gTrackContext.beginPath();
544 gTrackContext.moveTo(Math.round((gLastDrawnPoint.x - gPos.x) / gZoomFactor + gMapCanvas.width / 2),
545 Math.round((gLastDrawnPoint.y - gPos.y) / gZoomFactor + gMapCanvas.height / 2));
14e6d3ad 546 }
4b1d0915 547 gTrackContext.lineTo(mappos.x, mappos.y);
14e6d3ad 548 if (!trackpoint.optimized)
4b1d0915 549 gTrackContext.stroke();
55c4a0b7 550 }
14e6d3ad 551 gLastDrawnPoint = trackpoint;
23cd2dcc
RK
552}
553
b054bd48 554function drawCurrentLocation(trackPoint) {
4b1d0915
RK
555 var locpoint = gps2xy(trackPoint.coords.latitude, trackPoint.coords.longitude);
556 var circleRadius = Math.round(gCurLocSize / 2);
557 var mappos = {x: Math.round((locpoint.x - gPos.x) / gZoomFactor + gMapCanvas.width / 2),
558 y: Math.round((locpoint.y - gPos.y) / gZoomFactor + gMapCanvas.height / 2)};
559
560 undrawCurrentLocation();
b054bd48
RK
561
562 // Cache overdrawn area.
4b1d0915
RK
563 gCurPosMapCache =
564 {point: locpoint,
565 radius: circleRadius,
566 data: gTrackContext.getImageData(mappos.x - circleRadius,
567 mappos.y - circleRadius,
568 circleRadius * 2, circleRadius * 2)};
569
570 gTrackContext.strokeStyle = gCurLocColor;
571 gTrackContext.fillStyle = gTrackContext.strokeStyle;
572 gTrackContext.beginPath();
573 gTrackContext.arc(mappos.x, mappos.y,
574 circleRadius, 0, Math.PI * 2, false);
575 gTrackContext.fill();
576}
577
578function undrawCurrentLocation() {
579 if (gCurPosMapCache) {
580 var oldpoint = gCurPosMapCache.point;
581 var oldmp = {x: Math.round((oldpoint.x - gPos.x) / gZoomFactor + gMapCanvas.width / 2),
582 y: Math.round((oldpoint.y - gPos.y) / gZoomFactor + gMapCanvas.height / 2)};
583 gTrackContext.putImageData(gCurPosMapCache.data,
584 oldmp.x - gCurPosMapCache.radius,
585 oldmp.y - gCurPosMapCache.radius);
586 gCurPosMapCache = undefined;
587 }
b054bd48
RK
588}
589
23cd2dcc
RK
590var mapEvHandler = {
591 handleEvent: function(aEvent) {
592 var touchEvent = aEvent.type.indexOf('touch') != -1;
593
306ae634
RK
594 if (touchEvent) {
595 aEvent.stopPropagation();
596 }
597
8389557a
RK
598 // Bail out if the event is happening on an input.
599 if (aEvent.target.tagName.toLowerCase() == "input")
600 return;
601
1222624d
RK
602 // Bail out on unwanted map moves, but not zoom or keyboard events.
603 if (aEvent.type.indexOf("mouse") === 0 || aEvent.type.indexOf("touch") === 0) {
23cd2dcc
RK
604 // Bail out if this is neither a touch nor left-click.
605 if (!touchEvent && aEvent.button != 0)
606 return;
607
608 // Bail out if the started touch can't be found.
4b12da3a
RK
609 if (touchEvent && gDragging &&
610 !aEvent.changedTouches.identifiedTouch(gDragTouchID))
23cd2dcc
RK
611 return;
612 }
613
614 var coordObj = touchEvent ?
4b12da3a 615 aEvent.changedTouches.identifiedTouch(gDragTouchID) :
23cd2dcc
RK
616 aEvent;
617
618 switch (aEvent.type) {
619 case "mousedown":
620 case "touchstart":
621 if (touchEvent) {
517c0099
RK
622 if (aEvent.targetTouches.length == 2) {
623 gPinchStartWidth = Math.sqrt(
624 Math.pow(aEvent.targetTouches.item(1).clientX -
625 aEvent.targetTouches.item(0).clientX, 2) +
626 Math.pow(aEvent.targetTouches.item(1).clientY -
627 aEvent.targetTouches.item(0).clientY, 2)
628 );
629 }
4b12da3a
RK
630 gDragTouchID = aEvent.changedTouches.item(0).identifier;
631 coordObj = aEvent.changedTouches.identifiedTouch(gDragTouchID);
23cd2dcc 632 }
4b1d0915
RK
633 var x = coordObj.clientX - gMapCanvas.offsetLeft;
634 var y = coordObj.clientY - gMapCanvas.offsetTop;
b395419b 635
23cd2dcc
RK
636 if (touchEvent || aEvent.button === 0) {
637 gDragging = true;
638 }
639 gLastMouseX = x;
640 gLastMouseY = y;
7a549148 641 showUI();
23cd2dcc
RK
642 break;
643 case "mousemove":
644 case "touchmove":
517c0099
RK
645 if (touchEvent && aEvent.targetTouches.length == 2) {
646 curPinchStartWidth = Math.sqrt(
647 Math.pow(aEvent.targetTouches.item(1).clientX -
648 aEvent.targetTouches.item(0).clientX, 2) +
649 Math.pow(aEvent.targetTouches.item(1).clientY -
650 aEvent.targetTouches.item(0).clientY, 2)
651 );
003d56f8
RK
652 if (!gPinchStartWidth)
653 gPinchStartWidth = curPinchStartWidth;
d07d7abc 654
517c0099
RK
655 if (gPinchStartWidth / curPinchStartWidth > 1.7 ||
656 gPinchStartWidth / curPinchStartWidth < 0.6) {
657 var newZoomLevel = gPos.z + (gPinchStartWidth < curPinchStartWidth ? 1 : -1);
658 if ((newZoomLevel >= 0) && (newZoomLevel <= gMaxZoom)) {
659 // Calculate new center of the map - preserve middle of pinch.
660 // This means that pixel distance between old center and middle
661 // must equal pixel distance of new center and middle.
662 var x = (aEvent.targetTouches.item(1).clientX +
663 aEvent.targetTouches.item(0).clientX) / 2 -
664 gMapCanvas.offsetLeft;
665 var y = (aEvent.targetTouches.item(1).clientY +
666 aEvent.targetTouches.item(0).clientY) / 2 -
667 gMapCanvas.offsetTop;
668
669 // Zoom factor after this action.
670 var newZoomFactor = Math.pow(2, gMaxZoom - newZoomLevel);
671 gPos.x -= (x - gMapCanvas.width / 2) * (newZoomFactor - gZoomFactor);
672 gPos.y -= (y - gMapCanvas.height / 2) * (newZoomFactor - gZoomFactor);
673
674 if (gPinchStartWidth < curPinchStartWidth)
675 zoomIn();
676 else
677 zoomOut();
003d56f8
RK
678
679 // Reset pinch start width and start another pinch gesture.
680 gPinchStartWidth = null;
517c0099
RK
681 }
682 }
d07d7abc 683 // If we are in a pinch, do not drag.
517c0099
RK
684 break;
685 }
4b1d0915
RK
686 var x = coordObj.clientX - gMapCanvas.offsetLeft;
687 var y = coordObj.clientY - gMapCanvas.offsetTop;
23cd2dcc
RK
688 if (gDragging === true) {
689 var dX = x - gLastMouseX;
690 var dY = y - gLastMouseY;
b395419b
RK
691 gPos.x -= dX * gZoomFactor;
692 gPos.y -= dY * gZoomFactor;
747bbd55
RK
693 if (true) { // use optimized path
694 var mapData = gMapContext.getImageData(0, 0,
695 gMapCanvas.width,
696 gMapCanvas.height);
697 gMapContext.clearRect(0, 0, gMapCanvas.width, gMapCanvas.height);
698 gMapContext.putImageData(mapData, dX, dY);
699 drawMap({left: (dX > 0) ? dX : 0,
700 right: (dX < 0) ? -dX : 0,
701 top: (dY > 0) ? dY : 0,
702 bottom: (dY < 0) ? -dY : 0});
703 }
704 else {
705 drawMap(false, true);
706 }
7a549148 707 showUI();
23cd2dcc
RK
708 }
709 gLastMouseX = x;
710 gLastMouseY = y;
711 break;
712 case "mouseup":
713 case "touchend":
d07d7abc 714 gPinchStartWidth = null;
23cd2dcc 715 gDragging = false;
7a549148 716 showUI();
23cd2dcc
RK
717 break;
718 case "mouseout":
719 case "touchcancel":
720 case "touchleave":
721 //gDragging = false;
722 break;
5fb31b29
RK
723 case "wheel":
724 // If we'd want pixels, we'd need to calc up using aEvent.deltaMode.
725 // See https://developer.mozilla.org/en-US/docs/Mozilla_event_reference/wheel
726
727 // Only accept (non-null) deltaY values
728 if (!aEvent.deltaY)
729 break;
23cd2dcc 730
55c4a0b7
RK
731 // Debug output: "coordinates" of the point the mouse was over.
732 /*
4b1d0915
RK
733 var ptCoord = {x: gPos.x + (x - gMapCanvas.width / 2) * gZoomFactor,
734 y: gPos.y + (x - gMapCanvas.height / 2) * gZoomFactor};
55c4a0b7
RK
735 var gpsCoord = xy2gps(ptCoord.x, ptCoord.y);
736 var pt2Coord = gps2xy(gpsCoord.latitude, gpsCoord.longitude);
915d4271
RK
737 console.log(ptCoord.x + "/" + ptCoord.y + " - " +
738 gpsCoord.latitude + "/" + gpsCoord.longitude + " - " +
739 pt2Coord.x + "/" + pt2Coord.y);
55c4a0b7 740 */
4b1d0915 741
5fb31b29 742 var newZoomLevel = gPos.z + (aEvent.deltaY < 0 ? 1 : -1);
4b1d0915
RK
743 if ((newZoomLevel >= 0) && (newZoomLevel <= gMaxZoom)) {
744 // Calculate new center of the map - same point stays under the mouse.
745 // This means that the pixel distance between the old center and point
746 // must equal the pixel distance of the new center and that point.
747 var x = coordObj.clientX - gMapCanvas.offsetLeft;
748 var y = coordObj.clientY - gMapCanvas.offsetTop;
749
750 // Zoom factor after this action.
751 var newZoomFactor = Math.pow(2, gMaxZoom - newZoomLevel);
752 gPos.x -= (x - gMapCanvas.width / 2) * (newZoomFactor - gZoomFactor);
753 gPos.y -= (y - gMapCanvas.height / 2) * (newZoomFactor - gZoomFactor);
754
5fb31b29 755 if (aEvent.deltaY < 0)
4b1d0915 756 zoomIn();
5fb31b29 757 else
4b1d0915
RK
758 zoomOut();
759 }
23cd2dcc 760 break;
1222624d
RK
761 case "keydown":
762 // Allow keyboard control to move and zoom the map.
763 // Should use aEvent.key instead of aEvent.which but needs bug 680830.
764 // See https://developer.mozilla.org/en-US/docs/DOM/Mozilla_event_reference/keydown
765 var dX = 0;
766 var dY = 0;
767 switch (aEvent.which) {
768 case 39: // right
769 dX = -gTileSize / 2;
770 break;
771 case 37: // left
772 dX = gTileSize / 2;
773 break;
774 case 38: // up
775 dY = gTileSize / 2;
776 break;
777 case 40: // down
778 dY = -gTileSize / 2;
779 break;
780 case 87: // w
781 case 107: // + (numpad)
782 case 171: // + (normal key)
783 zoomIn();
784 break;
785 case 83: // s
786 case 109: // - (numpad)
787 case 173: // - (normal key)
788 zoomOut();
789 break;
790 case 48: // 0
791 case 49: // 1
792 case 50: // 2
793 case 51: // 3
794 case 52: // 4
795 case 53: // 5
796 case 54: // 6
797 case 55: // 7
798 case 56: // 8
799 zoomTo(aEvent.which - 38);
800 break;
801 case 57: // 9
802 zoomTo(9);
803 break;
804 case 96: // 0 (numpad)
805 case 97: // 1 (numpad)
806 case 98: // 2 (numpad)
807 case 99: // 3 (numpad)
808 case 100: // 4 (numpad)
809 case 101: // 5 (numpad)
810 case 102: // 6 (numpad)
811 case 103: // 7 (numpad)
812 case 104: // 8 (numpad)
813 zoomTo(aEvent.which - 86);
814 break;
815 case 105: // 9 (numpad)
816 zoomTo(9);
817 break;
818 default: // not supported
819 console.log("key not supported: " + aEvent.which);
820 break;
821 }
822
823 // Move if needed.
824 if (dX || dY) {
825 gPos.x -= dX * gZoomFactor;
826 gPos.y -= dY * gZoomFactor;
827 if (true) { // use optimized path
828 var mapData = gMapContext.getImageData(0, 0,
829 gMapCanvas.width,
830 gMapCanvas.height);
831 gMapContext.clearRect(0, 0, gMapCanvas.width, gMapCanvas.height);
832 gMapContext.putImageData(mapData, dX, dY);
833 drawMap({left: (dX > 0) ? dX : 0,
834 right: (dX < 0) ? -dX : 0,
835 top: (dY > 0) ? dY : 0,
836 bottom: (dY < 0) ? -dY : 0});
837 }
838 else {
839 drawMap(false, true);
840 }
841 }
842 break;
23cd2dcc
RK
843 }
844 }
845};
55c4a0b7 846
993fd081 847var geofake = {
55c4a0b7 848 tracking: false,
4b12da3a 849 lastPos: {x: undefined, y: undefined},
55c4a0b7
RK
850 watchPosition: function(aSuccessCallback, aErrorCallback, aPrefObject) {
851 this.tracking = true;
852 var watchCall = function() {
4b12da3a
RK
853 // calc new position in lat/lon degrees
854 // 90° on Earth surface are ~10,000 km at the equator,
855 // so try moving at most 10m at a time
856 if (geofake.lastPos.x)
857 geofake.lastPos.x += (Math.random() - .5) * 90 / 1000000
858 else
859 geofake.lastPos.x = 48.208174
860 if (geofake.lastPos.y)
861 geofake.lastPos.y += (Math.random() - .5) * 90 / 1000000
862 else
863 geofake.lastPos.y = 16.373819
55c4a0b7 864 aSuccessCallback({timestamp: Date.now(),
4b12da3a
RK
865 coords: {latitude: geofake.lastPos.x,
866 longitude: geofake.lastPos.y,
55c4a0b7
RK
867 accuracy: 20}});
868 if (geofake.tracking)
869 setTimeout(watchCall, 1000);
870 };
871 setTimeout(watchCall, 1000);
872 return "foo";
873 },
874 clearWatch: function(aID) {
875 this.tracking = false;
876 }
877}
878
3610c22d
RK
879function setCentering(aCheckbox) {
880 if (gMapPrefsLoaded && mainDB)
881 gPrefs.set("center_map", aCheckbox.checked);
882 gCenterPosition = aCheckbox.checked;
883}
884
885function setTracking(aCheckbox) {
886 if (gMapPrefsLoaded && mainDB)
887 gPrefs.set("tracking_enabled", aCheckbox.checked);
888 if (aCheckbox.checked)
889 startTracking();
890 else
891 endTracking();
892}
893
55c4a0b7 894function startTracking() {
31f0fe16 895 if (gGeolocation) {
68afcd96
RK
896 gActionLabel.textContent = "Establishing Position";
897 gAction.style.display = "block";
4b12da3a 898 gGeoWatchID = gGeolocation.watchPosition(
55c4a0b7 899 function(position) {
68afcd96
RK
900 if (gActionLabel.textContent) {
901 gActionLabel.textContent = "";
902 gAction.style.display = "none";
903 }
55c4a0b7 904 // Coords spec: https://developer.mozilla.org/en/XPCOM_Interface_Reference/NsIDOMGeoPositionCoords
993fd081 905 var tPoint = {time: position.timestamp,
31f0fe16
RK
906 coords: {latitude: position.coords.latitude,
907 longitude: position.coords.longitude,
908 altitude: position.coords.altitude,
909 accuracy: position.coords.accuracy,
910 altitudeAccuracy: position.coords.altitudeAccuracy,
911 heading: position.coords.heading,
912 speed: position.coords.speed},
993fd081 913 beginSegment: !gLastTrackPoint};
b054bd48
RK
914 // Only add point to track is accuracy is good enough.
915 if (tPoint.coords.accuracy < gMinTrackAccuracy) {
916 gLastTrackPoint = tPoint;
917 gTrack.push(tPoint);
918 try { gTrackStore.push(tPoint); } catch(e) {}
919 var redrawn = false;
920 if (gCenterPosition) {
921 var posCoord = gps2xy(position.coords.latitude,
922 position.coords.longitude);
4b1d0915
RK
923 if (Math.abs(gPos.x - posCoord.x) > gMapCanvas.width * gZoomFactor / 4 ||
924 Math.abs(gPos.y - posCoord.y) > gMapCanvas.height * gZoomFactor / 4) {
b054bd48
RK
925 gPos.x = posCoord.x;
926 gPos.y = posCoord.y;
927 drawMap(); // This draws the current point as well.
928 redrawn = true;
929 }
99631a75 930 }
b054bd48 931 if (!redrawn)
4b1d0915 932 undrawCurrentLocation();
b054bd48 933 drawTrackPoint(position.coords.latitude, position.coords.longitude, true);
05c21757 934 }
b054bd48 935 drawCurrentLocation(tPoint);
55c4a0b7
RK
936 },
937 function(error) {
938 // Ignore erros for the moment, but this is good for debugging.
939 // See https://developer.mozilla.org/en/Using_geolocation#Handling_errors
915d4271
RK
940 if (gDebug)
941 console.log(error.message);
55c4a0b7
RK
942 },
943 {enableHighAccuracy: true}
944 );
945 }
946}
947
948function endTracking() {
68afcd96
RK
949 if (gActionLabel.textContent) {
950 gActionLabel.textContent = "";
951 gAction.style.display = "none";
952 }
55c4a0b7 953 if (gGeoWatchID) {
4b12da3a 954 gGeolocation.clearWatch(gGeoWatchID);
55c4a0b7
RK
955 }
956}
993fd081
RK
957
958function clearTrack() {
959 gTrack = [];
960 gTrackStore.clear();
6ddefbf9 961 drawTrack();
993fd081 962}
a8634d37
RK
963
964var gTileService = {
965 objStore: "tilecache",
966
5d67397a 967 ageLimit: 14 * 86400 * 1000, // 2 weeks (in ms)
3431f496 968
a8634d37
RK
969 get: function(aStyle, aCoords, aCallback) {
970 var norm = normalizeCoords(aCoords);
971 var dbkey = aStyle + "::" + norm.x + "," + norm.y + "," + norm.z;
972 this.getDBCache(dbkey, function(aResult, aEvent) {
973 if (aResult) {
974 // We did get a cached object.
a8634d37 975 aCallback(aResult.image, aStyle, aCoords);
3431f496 976 // Look at the timestamp and return if it's not too old.
5d67397a 977 if (aResult.timestamp + gTileService.ageLimit > Date.now())
3431f496
RK
978 return;
979 // Reload cached tile otherwise.
5d67397a
RK
980 var oldDate = new Date(aResult.timestamp);
981 console.log("reload cached tile: " + dbkey + " - " + oldDate.toUTCString());
a8634d37 982 }
3431f496
RK
983 // Retrieve image from the web and store it in the cache.
984 var XHR = new XMLHttpRequest();
985 XHR.open("GET",
986 gMapStyles[aStyle].url
987 .replace("{x}", norm.x)
988 .replace("{y}", norm.y)
989 .replace("{z}", norm.z)
990 .replace("[a-c]", String.fromCharCode(97 + Math.floor(Math.random() * 2)))
991 .replace("[1-4]", 1 + Math.floor(Math.random() * 3)),
992 true);
993 XHR.responseType = "blob";
994 XHR.addEventListener("load", function () {
995 if (XHR.status === 200) {
996 var blob = XHR.response;
3431f496 997 aCallback(blob, aStyle, aCoords);
6d7cdcf6 998 gTileService.setDBCache(dbkey, {image: blob, timestamp: Date.now()});
3431f496
RK
999 }
1000 }, false);
1001 XHR.send();
a8634d37
RK
1002 });
1003 },
1004
1005 getDBCache: function(aKey, aCallback) {
1006 if (!mainDB)
1007 return;
1008 var transaction = mainDB.transaction([this.objStore]);
1009 var request = transaction.objectStore(this.objStore).get(aKey);
1010 request.onsuccess = function(event) {
1011 aCallback(request.result, event);
1012 };
1013 request.onerror = function(event) {
1014 // Errors can be handled here.
1015 aCallback(undefined, event);
1016 };
1017 },
1018
1019 setDBCache: function(aKey, aValue, aCallback) {
1020 if (!mainDB)
1021 return;
1022 var success = false;
1023 var transaction = mainDB.transaction([this.objStore], "readwrite");
1024 var objStore = transaction.objectStore(this.objStore);
1025 var request = objStore.put(aValue, aKey);
1026 request.onsuccess = function(event) {
1027 success = true;
1028 if (aCallback)
1029 aCallback(success, event);
1030 };
1031 request.onerror = function(event) {
1032 // Errors can be handled here.
1033 if (aCallback)
1034 aCallback(success, event);
1035 };
1036 },
1037
1038 unsetDBCache: function(aKey, aCallback) {
1039 if (!mainDB)
1040 return;
1041 var success = false;
1042 var transaction = mainDB.transaction([this.objStore], "readwrite");
1043 var request = transaction.objectStore(this.objStore).delete(aKey);
1044 request.onsuccess = function(event) {
1045 success = true;
1046 if (aCallback)
1047 aCallback(success, event);
1048 };
1049 request.onerror = function(event) {
1050 // Errors can be handled here.
1051 if (aCallback)
1052 aCallback(success, event);
1053 }
3431f496
RK
1054 },
1055
1056 clearDB: function(aCallback) {
1057 if (!mainDB)
1058 return;
1059 var success = false;
1060 var transaction = mainDB.transaction([this.objStore], "readwrite");
1061 var request = transaction.objectStore(this.objStore).clear();
1062 request.onsuccess = function(event) {
1063 success = true;
1064 if (aCallback)
1065 aCallback(success, event);
1066 };
1067 request.onerror = function(event) {
1068 // Errors can be handled here.
1069 if (aCallback)
1070 aCallback(success, event);
1071 }
a8634d37
RK
1072 }
1073};