add OpenTopoMap support
[lantea.git] / js / map.js
index d85d494b84c4f7b9bc6c478c3c0482f770f60cfe..ac50370bb0f97433944bd777a4b932addacaf3d1 100644 (file)
--- a/js/map.js
+++ b/js/map.js
@@ -6,10 +6,7 @@ var gGLMapCanvas, gTrackCanvas, gGeolocation;
 var gDebug = false;
 
 var gMinTrackAccuracy = 1000; // meters
-var gTrackWidth = 2; // pixels
-var gTrackColor = "#FF0000";
-var gCurLocSize = 6; // pixels
-var gCurLocColor = "#A00000";
+var gCenterDelayAfterMove = 3000; // milliseconds
 
 var gMapStyles = {
   // OSM tile usage policy: http://wiki.openstreetmap.org/wiki/Tile_usage_policy
@@ -52,6 +49,11 @@ var gMapStyles = {
      url: "https://tilecache[1-4].kairo.at/osmhot/{z}/{x}/{y}.png", // route through CORS+SSL tilecache @ kairo.at
      //url: "http://[a-c].tile.openstreetmap.fr/hot/{z}/{x}/{y}.png", // https has CAcert which doesn't work in browsers
      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>'},
+  opentopomap:
+    {name: "OpenTopoMap",
+     //url: "https://tilecache[1-4].kairo.at/opentopomap/{z}/{x}/{y}.png", // route through tilecache @ kairo.at
+     url: "https://[a-c].tile.opentopomap.org/{z}/{x}/{y}.png",
+     copyright: 'Map data: © <a href="https://openstreetmap.org/copyright">OpenStreetMap</a> contributors, <a href="http://viewfinderpanoramas.org">SRTM</a> | map style: © <a href="https://opentopomap.org">OpenTopoMap</a> (<a href="https://creativecommons.org/licenses/by-sa/3.0/">CC-BY-SA</a>)'},
   hikebike:
     {name: "Hike and Bike (OSM)",
      url: "https://tilecache[1-4].kairo.at/hikebike/{z}/{x}/{y}.png", // route through CORS+SSL tilecache @ kairo.at
@@ -92,6 +94,7 @@ var gGeoWatchID, gGPSWakeLock;
 var gTrack = [];
 var gLastTrackPoint;
 var gCenterPosition = true;
+var gLastMoveAction = null;
 
 function initMap() {
   gGeolocation = navigator.geolocation;
@@ -238,7 +241,7 @@ function loadPrefs(aEvent) {
         // (but clamped to the first value over a certain limit).
         // Initial paint will do initial track drawing.
         if (tracklen % redrawBase == 0) {
-          gTrackLayer.drawTrack();
+          gTrackLayer.drawTrack(); // TODO: we could draw incremmentally if we would support reverse-direction drawing...
           if (redrawBase < 1000) {
             redrawBase = tracklen;
           }
@@ -246,7 +249,7 @@ function loadPrefs(aEvent) {
       }
       else {
         // Last point received.
-        gTrackLayer.drawTrack();
+        gTrackLayer.drawTrack(); // TODO: we could draw incremmentally if we would support reverse-direction drawing...
       }
       if (!trackLoadStarted) {
         // We have the most recent point, if present, rest will load async.
@@ -645,32 +648,69 @@ var gTrackLayer = {
   context: null,
   curPosMapCache: undefined,
   lastDrawnPoint: null,
-  drawing: false,
+  lastRequestedIndex: null, // may not have been actually drawn...
+  drawRequested: false,
+  restartDrawing: true,
+
+  maxDrawTime: 10, // max time allowed for drawing a section, in ms - 10 means we can do 100 fps smoothly
+  trackWidth: 2, // pixels
+  trackColor: "#FF0000",
+  curLocSize: 6, // pixels
+  curLocColor: "#A00000",
+
+  drawTrack: function(needRestart = true) {
+    // TODO: figure out if we can support reverse drawing while initially loading the track.
+    // Only draw if we're actually visible.
+    // Also, avoid running this multiple times when it could not complete yet.
+    if (needRestart) { gTrackLayer.restartDrawing = true; }
+    if (gTrackLayer.context && document.hidden != true && !gTrackLayer.drawRequested) {
+      gTrackLayer.drawRequested = true;
+      window.requestAnimationFrame(gTrackLayer.drawTrackSection);
+    }
+  },
 
-  drawTrack: function() {
-    if (gTrackLayer.drawing) { return; }
-    gTrackLayer.drawing = true;
-    //performance.now()
-    if (gTrackLayer.context && (document.hidden != true)) { // Only draw if we're actually visible.
+  drawTrackSection: function(aTimestamp) {
+    var start = performance.now();
+    if (gTrackLayer.restartDrawing) {
+      gTrackLayer.lastRequestedIndex = 0;
       gTrackLayer.lastDrawnPoint = null;
+      gTrackLayer.restartDrawing = false;
       gTrackLayer.curPosMapCache = undefined;
       gTrackLayer.context.clearRect(0, 0, gTrackCanvas.width, gTrackCanvas.height);
-      if (gTrack.length) {
-        for (var i = 0; i < gTrack.length; i++) {
-          gTrackLayer.drawTrackPoint(gTrack[i].coords.latitude, gTrack[i].coords.longitude,
-                                     (i + 1 >= gTrack.length || gTrack[i+1].beginSegment));
+    }
+    if (gTrack.length && (performance.now() < start + gTrackLayer.maxDrawTime)) {
+      for (; gTrackLayer.lastRequestedIndex < gTrack.length; gTrackLayer.lastRequestedIndex++) {
+        gTrackLayer.drawTrackPoint(gTrackLayer.lastRequestedIndex);
+        if (performance.now() >= start + gTrackLayer.maxDrawTime) {
+          // Break out of the loop if we are over the max allowed time, we'll continue in the next rAF (see below).
+          gTrackLayer.drawTrackPoint(null);
+          break;
         }
       }
     }
-    gTrackLayer.drawing = false;
+    // If we still have work to do and we're still visible, continue drawing.
+    if ((gTrackLayer.lastRequestedIndex + 1 < gTrack.length) && gTrackLayer.context && (document.hidden != true)) {
+      window.requestAnimationFrame(gTrackLayer.drawTrackSection);
+    }
+    gTrackLayer.drawRequested = false;
   },
 
-  drawTrackPoint: function(aLatitude, aLongitude, aLastPoint) {
-    var trackpoint = {"worldpos": gps2xy(aLatitude, aLongitude)};
+  drawTrackPoint: function(aIndex) {
+    gTrackLayer.running = true;
+    if (!aIndex && aIndex !== 0) {
+      // We can be called this way to make sure we draw an actual line up to where we are right now.
+      if (gTrackLayer.lastDrawnPoint && gTrackLayer.lastDrawnPoint.optimized) {
+        gTrackLayer.context.stroke();
+        gTrackLayer.lastDrawnPoint.optimized = false;
+      }
+      return;
+    }
+    var trackpoint = {"worldpos": gps2xy(gTrack[aIndex].coords.latitude, gTrack[aIndex].coords.longitude)};
+    var isLastPoint = (aIndex + 1 >= gTrack.length || gTrack[aIndex+1].beginSegment);
     var update_drawnpoint = true;
     // lastPoint is for optimizing (not actually executing the draw until the last)
-    trackpoint.segmentEnd = (aLastPoint === true);
-    trackpoint.optimized = (aLastPoint === false);
+    trackpoint.segmentEnd = (isLastPoint === true);
+    trackpoint.optimized = (isLastPoint === false);
     trackpoint.mappos = {x: Math.round((trackpoint.worldpos.x - gMap.pos.x) / gMap.zoomFactor + gMap.width / 2),
                          y: Math.round((trackpoint.worldpos.y - gMap.pos.y) / gMap.zoomFactor + gMap.height / 2)};
     trackpoint.skip_drawing = false;
@@ -684,9 +724,9 @@ var gTrackLayer = {
       }
     }
     if (!gTrackLayer.lastDrawnPoint || !gTrackLayer.lastDrawnPoint.optimized) {
-      gTrackLayer.context.strokeStyle = gTrackColor;
+      gTrackLayer.context.strokeStyle = gTrackLayer.trackColor;
       gTrackLayer.context.fillStyle = gTrackLayer.context.strokeStyle;
-      gTrackLayer.context.lineWidth = gTrackWidth;
+      gTrackLayer.context.lineWidth = gTrackLayer.trackWidth;
       gTrackLayer.context.lineCap = "round";
       gTrackLayer.context.lineJoin = "round";
     }
@@ -734,38 +774,44 @@ var gTrackLayer = {
   },
 
   drawCurrentLocation: function(trackPoint) {
-    var locpoint = gps2xy(trackPoint.coords.latitude, trackPoint.coords.longitude);
-    var circleRadius = Math.round(gCurLocSize / 2);
-    var mappos = {x: Math.round((locpoint.x - gMap.pos.x) / gMap.zoomFactor + gMap.width / 2),
-                  y: Math.round((locpoint.y - gMap.pos.y) / gMap.zoomFactor + gMap.height / 2)};
-
-    gTrackLayer.undrawCurrentLocation();
-
-    // Cache overdrawn area.
-    gTrackLayer.curPosMapCache =
-        {point: locpoint,
-        radius: circleRadius,
-        data: gTrackLayer.context.getImageData(mappos.x - circleRadius,
-                                               mappos.y - circleRadius,
-                                               circleRadius * 2, circleRadius * 2)};
-
-    gTrackLayer.context.strokeStyle = gCurLocColor;
-    gTrackLayer.context.fillStyle = gTrackLayer.context.strokeStyle;
-    gTrackLayer.context.beginPath();
-    gTrackLayer.context.arc(mappos.x, mappos.y,
-                            circleRadius, 0, Math.PI * 2, false);
-    gTrackLayer.context.fill();
+    // Only run this when visible and we are not drawing a track right now.
+    if (gTrackLayer.context && document.hidden != true && !gTrackLayer.drawRequested) {
+      var locpoint = gps2xy(trackPoint.coords.latitude, trackPoint.coords.longitude);
+      var circleRadius = Math.round(gTrackLayer.curLocSize / 2);
+      var mappos = {x: Math.round((locpoint.x - gMap.pos.x) / gMap.zoomFactor + gMap.width / 2),
+                    y: Math.round((locpoint.y - gMap.pos.y) / gMap.zoomFactor + gMap.height / 2)};
+
+      gTrackLayer.undrawCurrentLocation();
+
+      // Cache overdrawn area.
+      gTrackLayer.curPosMapCache =
+          {point: locpoint,
+          radius: circleRadius,
+          data: gTrackLayer.context.getImageData(mappos.x - circleRadius,
+                                                mappos.y - circleRadius,
+                                                circleRadius * 2, circleRadius * 2)};
+
+      gTrackLayer.context.strokeStyle = gTrackLayer.curLocColor;
+      gTrackLayer.context.fillStyle = gTrackLayer.context.strokeStyle;
+      gTrackLayer.context.beginPath();
+      gTrackLayer.context.arc(mappos.x, mappos.y,
+                              circleRadius, 0, Math.PI * 2, false);
+      gTrackLayer.context.fill();
+    }
   },
 
   undrawCurrentLocation: function() {
-    if (gTrackLayer.curPosMapCache) {
-      var oldpoint = gTrackLayer.curPosMapCache.point;
-      var oldmp = {x: Math.round((oldpoint.x - gMap.pos.x) / gMap.zoomFactor + gMap.width / 2),
-                   y: Math.round((oldpoint.y - gMap.pos.y) / gMap.zoomFactor + gMap.height / 2)};
-      gTrackLayer.context.putImageData(gTrackLayer.curPosMapCache.data,
-                                       oldmp.x - gTrackLayer.curPosMapCache.radius,
-                                       oldmp.y - gTrackLayer.curPosMapCache.radius);
-      gTrackLayer.curPosMapCache = undefined;
+    // Only run this when visible and we are not drawing a track right now.
+    if (gTrackLayer.context && document.hidden != true && !gTrackLayer.drawRequested) {
+      if (gTrackLayer.curPosMapCache) {
+        var oldpoint = gTrackLayer.curPosMapCache.point;
+        var oldmp = {x: Math.round((oldpoint.x - gMap.pos.x) / gMap.zoomFactor + gMap.width / 2),
+                    y: Math.round((oldpoint.y - gMap.pos.y) / gMap.zoomFactor + gMap.height / 2)};
+        gTrackLayer.context.putImageData(gTrackLayer.curPosMapCache.data,
+                                        oldmp.x - gTrackLayer.curPosMapCache.radius,
+                                        oldmp.y - gTrackLayer.curPosMapCache.radius);
+        gTrackLayer.curPosMapCache = undefined;
+      }
     }
   }
 }
@@ -935,6 +981,7 @@ var mapEvHandler = {
         gLastMouseX = x;
         gLastMouseY = y;
         showUI();
+        gLastMoveAction = performance.now();
         break;
       case "mousemove":
       case "touchmove":
@@ -991,6 +1038,7 @@ var mapEvHandler = {
         }
         gLastMouseX = x;
         gLastMouseY = y;
+        gLastMoveAction = performance.now();
         break;
       case "mouseup":
       case "touchend":
@@ -1108,6 +1156,7 @@ var mapEvHandler = {
           gMap.pos.x -= dX * gMap.zoomFactor;
           gMap.pos.y -= dY * gMap.zoomFactor;
           gMap.draw();
+          gLastMoveAction = performance.now();
         }
         break;
     }
@@ -1199,7 +1248,7 @@ function startTracking() {
           gTrack.push(tPoint);
           try { gTrackStore.push(tPoint); } catch(e) {}
           var redrawn = false;
-          if (gCenterPosition) {
+          if (gCenterPosition && (!gLastMoveAction || performance.now() > gLastMoveAction + gCenterDelayAfterMove)) {
             var posCoord = gps2xy(position.coords.latitude,
                                   position.coords.longitude);
             if (Math.abs(gMap.pos.x - posCoord.x) > gMap.width * gMap.zoomFactor / 4 ||
@@ -1212,7 +1261,7 @@ function startTracking() {
           }
           if (!redrawn)
             gTrackLayer.undrawCurrentLocation();
-            gTrackLayer.drawTrackPoint(position.coords.latitude, position.coords.longitude, true);
+            gTrackLayer.drawTrackPoint(gTrack.length-1);
         }
         gTrackLayer.drawCurrentLocation(tPoint);
       },