1 /* ***** BEGIN LICENSE BLOCK *****
2 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
4 * The contents of this file are subject to the Mozilla Public License Version
5 * 1.1 (the "License"); you may not use this file except in compliance with
6 * the License. You may obtain a copy of the License at
7 * http://www.mozilla.org/MPL/
9 * Software distributed under the License is distributed on an "AS IS" basis,
10 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
11 * for the specific language governing rights and limitations under the
14 * The Original Code is Lantea mapping/tracking web app.
16 * The Initial Developer of the Original Code is
17 * Robert Kaiser <kairo@kairo.at>.
18 * Portions created by the Initial Developer are Copyright (C) 2011
19 * the Initial Developer. All Rights Reserved.
22 * Robert Kaiser <kairo@kairo.at>
24 * Alternatively, the contents of this file may be used under the terms of
25 * either the GNU General Public License Version 2 or later (the "GPL"), or
26 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
27 * in which case the provisions of the GPL or the LGPL are applicable instead
28 * of those above. If you wish to allow use of your version of this file only
29 * under the terms of either the GPL or the LGPL, and not to allow others to
30 * use your version of this file under the terms of the MPL, indicate your
31 * decision by deleting the provisions above and replace them with the notice
32 * and other provisions required by the GPL or the LGPL. If you do not delete
33 * the provisions above, a recipient may use your version of this file under
34 * the terms of any one of the MPL, the GPL or the LGPL.
36 * ***** END LICENSE BLOCK ***** */
38 var gCanvas, gContext;
41 var gMaxZoom = 18; // The minimum is 0.
45 var gPos = {x: 35630000.0, // Current position in the map in pixels at the maximum zoom level (18)
46 y: 23670000.0, // The range is 0-67108864 (2^gMaxZoom * gTileSize)
47 z: 5.0}; // This can be fractional if we are between zoom levels.
52 // Used as an assiciative array. They keys have to be strings, ours will be "xindex,yindex,zindex" e.g. "13,245,12".
55 var gDragging = false;
58 window.onload = function() {
59 gCanvas = document.getElementById("map");
60 gContext = gCanvas.getContext("2d");
62 gCanvas.addEventListener("mouseup", mapEvHandler, false);
63 gCanvas.addEventListener("mousemove", mapEvHandler, false);
64 gCanvas.addEventListener("mousedown", mapEvHandler, false);
65 gCanvas.addEventListener("mouseout", mapEvHandler, false);
67 gCanvas.addEventListener("touchstart", mapEvHandler, false);
68 gCanvas.addEventListener("touchmove", mapEvHandler, false);
69 gCanvas.addEventListener("touchend", mapEvHandler, false);
70 gCanvas.addEventListener("touchcancel", mapEvHandler, false);
71 gCanvas.addEventListener("touchleave", mapEvHandler, false);
73 gCanvas.addEventListener("DOMMouseScroll", mapEvHandler, false);
74 gCanvas.addEventListener("mousewheel", mapEvHandler, false);
79 window.onresize = function() {
83 function resizeAndDraw() {
84 var viewportWidth = window.innerWidth;
85 var viewportHeight = window.innerHeight;
87 var canvasWidth = viewportWidth * 0.98;
88 var canvasHeight = (viewportHeight-110) * 0.98;
89 gCanvas.style.position = "fixed";
90 gCanvas.width = canvasWidth;
91 gCanvas.height = canvasHeight;
96 if (gPos.z < gMaxZoom) {
109 // A sane mod function that works for negative numbers.
112 return ((a % b) + b) % b;
115 function normaliseIndices(x, y, z) {
116 return {x: mod(x, Math.pow(2, z)),
117 y: mod(y, Math.pow(2, z)),
121 function tileURL(x, y, z) {
122 var norm = normaliseIndices(x, y, z);
123 var url = "http://tile.openstreetmap.org/" + norm.z + "/" + norm.x + "/" + norm.y + ".png";
127 // Returns true if the tile is outside the current view.
128 function isOutsideWindow(t) {
129 var pos = decodeIndex(t);
134 var wid = gCanvas.width * Math.pow(2, gMaxZoom - z);
135 var ht = gCanvas.height * Math.pow(2, gMaxZoom - z);
137 x *= Math.pow(2, gMaxZoom - z);
138 y *= Math.pow(2, gMaxZoom - z);
140 var sz = gTileSize * Math.pow(2, gMaxZoom - z);
141 if (x > gPos.x + wid / 2 || y > gPos.y + ht / 2 ||
142 x + sz < gPos.x - wid / 2 || y - sz < gPos.y - ht / 2)
147 function encodeIndex(x, y, z) {
148 var norm = normaliseIndices(x, y, z);
149 return norm.x + "," + norm.y + "," + norm.z;
152 function decodeIndex(encodedIdx) {
153 return encodedIdx.split(",", 3);
157 // Go through all the currently loaded tiles. If we don't want any of them remove them.
158 // for (t in gTiles) {
159 // if (isOutsideWindow(t))
162 var z = Math.round(gPos.z);
163 var wid = gCanvas.width * Math.pow(2, gMaxZoom - z); // Width in level 18 pixels.
164 var ht = gCanvas.height * Math.pow(2, gMaxZoom - z); // Height in level 18 pixels.
165 var sz = gTileSize * Math.pow(2, gMaxZoom - z); // Tile size in level 18 pixels.
167 var xMin = gPos.x - wid / 2; // Corners of the window in level 18 pixels.
168 var yMin = gPos.y - ht / 2;
169 var xMax = gPos.x + wid / 2;
170 var yMax = gPos.y + ht / 2;
172 // Go through all the tiles we want. If any of them aren't loaded or being loaded, do so.
173 for (var x = Math.floor(xMin / sz); x < Math.ceil(xMax / sz); ++x) {
174 for (var y = Math.floor(yMin / sz); y < Math.ceil(yMax / sz); ++y) {
175 var xoff = (x * sz - xMin) / Math.pow(2, gMaxZoom - z);
176 var yoff = (y * sz - yMin) / Math.pow(2, gMaxZoom - z);
177 var tileKey = encodeIndex(x, y, z);
178 if (gTiles[tileKey] && gTiles[tileKey].complete) {
179 // Round here is **CRUICIAL** otherwise the images are filtered and the performance sucks (more than expected).
180 gContext.drawImage(gTiles[tileKey], Math.round(xoff), Math.round(yoff));
183 if (!gTiles[tileKey]) {
184 gTiles[tileKey] = new Image();
185 gTiles[tileKey].src = tileURL(x, y, gPos.z);
186 gTiles[tileKey].onload = function() {
187 // TODO: Just render this tile where it should be.
188 // context.drawImage(gTiles[tileKey], Math.round(xoff), Math.round(yoff)); // Doesn't work for some reason.
192 gContext.fillStyle = "#ffffff";
193 gContext.fillRect(Math.round(xoff), Math.round(yoff), gTileSize, gTileSize);
200 handleEvent: function(aEvent) {
201 var touchEvent = aEvent.type.indexOf('touch') != -1;
203 // Bail out on unwanted map moves, but not mousewheel events.
204 if (aEvent.type != "DOMMouseScroll" && aEvent.type != "mousewheel") {
205 // Bail out if this is neither a touch nor left-click.
206 if (!touchEvent && aEvent.button != 0)
209 // Bail out if the started touch can't be found.
210 if (touchEvent && zoomstart &&
211 !aEvent.changedTouches.identifiedTouch(gZoomTouchID))
215 var coordObj = touchEvent ?
216 aEvent.changedTouches.identifiedTouch(gZoomTouchID) :
219 switch (aEvent.type) {
223 zoomTouchID = aEvent.changedTouches.item(0).identifier;
224 coordObj = aEvent.changedTouches.identifiedTouch(gZoomTouchID);
226 var x = coordObj.clientX - gCanvas.offsetLeft;
227 var y = coordObj.clientY - gCanvas.offsetTop;
228 if (touchEvent || aEvent.button === 0) {
236 var x = coordObj.clientX - gCanvas.offsetLeft;
237 var y = coordObj.clientY - gCanvas.offsetTop;
238 if (gDragging === true) {
239 var dX = x - gLastMouseX;
240 var dY = y - gLastMouseY;
241 gPos.x -= dX * Math.pow(2, gMaxZoom - gPos.z);
242 gPos.y -= dY * Math.pow(2, gMaxZoom - gPos.z);
257 case "DOMMouseScroll":
260 if (aEvent.wheelDelta) {
261 delta = aEvent.wheelDelta / 120;
265 else if (aEvent.detail) {
266 delta = -aEvent.detail / 3;