add actual maps to Lantea
[lantea.git] / js / map.js
CommitLineData
23cd2dcc
RK
1/* ***** BEGIN LICENSE BLOCK *****
2 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
3 *
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/
8 *
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
12 * License.
13 *
14 * The Original Code is Lantea mapping/tracking web app.
15 *
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.
20 *
21 * Contributor(s):
22 * Robert Kaiser <kairo@kairo.at>
23 *
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.
35 *
36 * ***** END LICENSE BLOCK ***** */
37
38var gCanvas, gContext;
39
40var gTileSize = 256;
41var gMaxZoom = 18; // The minimum is 0.
42
43//drawMap();
44
45var 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.
48
49var gLastMouseX = 0;
50var gLastMouseY = 0;
51
52// Used as an assiciative array. They keys have to be strings, ours will be "xindex,yindex,zindex" e.g. "13,245,12".
53var gTiles = {};
54
55var gDragging = false;
56var gZoomTouchID;
57
58window.onload = function() {
59 gCanvas = document.getElementById("map");
60 gContext = gCanvas.getContext("2d");
61
62 gCanvas.addEventListener("mouseup", mapEvHandler, false);
63 gCanvas.addEventListener("mousemove", mapEvHandler, false);
64 gCanvas.addEventListener("mousedown", mapEvHandler, false);
65 gCanvas.addEventListener("mouseout", mapEvHandler, false);
66
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);
72
73 gCanvas.addEventListener("DOMMouseScroll", mapEvHandler, false);
74 gCanvas.addEventListener("mousewheel", mapEvHandler, false);
75
76 resizeAndDraw();
77}
78
79window.onresize = function() {
80 resizeAndDraw();
81}
82
83function resizeAndDraw() {
84 var viewportWidth = window.innerWidth;
85 var viewportHeight = window.innerHeight;
86
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;
92 drawMap();
93}
94
95function zoomIn() {
96 if (gPos.z < gMaxZoom) {
97 gPos.z++;
98 drawMap();
99 }
100}
101
102function zoomOut() {
103 if (gPos.z > 0) {
104 gPos.z--;
105 drawMap();
106 }
107}
108
109// A sane mod function that works for negative numbers.
110// Returns a % b.
111function mod(a, b) {
112 return ((a % b) + b) % b;
113}
114
115function normaliseIndices(x, y, z) {
116 return {x: mod(x, Math.pow(2, z)),
117 y: mod(y, Math.pow(2, z)),
118 z: z};
119}
120
121function 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";
124 return url;
125}
126
127// Returns true if the tile is outside the current view.
128function isOutsideWindow(t) {
129 var pos = decodeIndex(t);
130 var x = pos[0];
131 var y = pos[1];
132 var z = pos[2];
133
134 var wid = gCanvas.width * Math.pow(2, gMaxZoom - z);
135 var ht = gCanvas.height * Math.pow(2, gMaxZoom - z);
136
137 x *= Math.pow(2, gMaxZoom - z);
138 y *= Math.pow(2, gMaxZoom - z);
139
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)
143 return true;
144 return false;
145}
146
147function encodeIndex(x, y, z) {
148 var norm = normaliseIndices(x, y, z);
149 return norm.x + "," + norm.y + "," + norm.z;
150}
151
152function decodeIndex(encodedIdx) {
153 return encodedIdx.split(",", 3);
154}
155
156function drawMap() {
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))
160 // delete gTiles[t];
161 // }
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.
166
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;
171
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));
181 }
182 else {
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.
189 drawMap();
190 }
191 }
192 gContext.fillStyle = "#ffffff";
193 gContext.fillRect(Math.round(xoff), Math.round(yoff), gTileSize, gTileSize);
194 }
195 }
196 }
197}
198
199var mapEvHandler = {
200 handleEvent: function(aEvent) {
201 var touchEvent = aEvent.type.indexOf('touch') != -1;
202
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)
207 return;
208
209 // Bail out if the started touch can't be found.
210 if (touchEvent && zoomstart &&
211 !aEvent.changedTouches.identifiedTouch(gZoomTouchID))
212 return;
213 }
214
215 var coordObj = touchEvent ?
216 aEvent.changedTouches.identifiedTouch(gZoomTouchID) :
217 aEvent;
218
219 switch (aEvent.type) {
220 case "mousedown":
221 case "touchstart":
222 if (touchEvent) {
223 zoomTouchID = aEvent.changedTouches.item(0).identifier;
224 coordObj = aEvent.changedTouches.identifiedTouch(gZoomTouchID);
225 }
226 var x = coordObj.clientX - gCanvas.offsetLeft;
227 var y = coordObj.clientY - gCanvas.offsetTop;
228 if (touchEvent || aEvent.button === 0) {
229 gDragging = true;
230 }
231 gLastMouseX = x;
232 gLastMouseY = y;
233 break;
234 case "mousemove":
235 case "touchmove":
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);
243 drawMap();
244 }
245 gLastMouseX = x;
246 gLastMouseY = y;
247 break;
248 case "mouseup":
249 case "touchend":
250 gDragging = false;
251 break;
252 case "mouseout":
253 case "touchcancel":
254 case "touchleave":
255 //gDragging = false;
256 break;
257 case "DOMMouseScroll":
258 case "mousewheel":
259 var delta = 0;
260 if (aEvent.wheelDelta) {
261 delta = aEvent.wheelDelta / 120;
262 if (window.opera)
263 delta = -delta;
264 }
265 else if (aEvent.detail) {
266 delta = -aEvent.detail / 3;
267 }
268
269 if (delta > 0)
270 zoomIn();
271 else if (delta < 0)
272 zoomOut();
273 break;
274 }
275 }
276};