<commit_before>function setStatus(msg, options) {
  $("#status-msg").text(msg);
  var statusclass = options && options.class ? options.class : "status-info";
  $("#status-msg").attr("class", statusclass);
  var showspinner = options && !isUnset(options.spinner) && options.spinner;
  $("#spinner").toggle(showspinner);
  $("#status").fadeIn();
  if (options && options.timeout) {
    setTimeout(function(){ clearStatus(); }, 1000*options.timeout);
  }
}

function clearStatus() {
  $("#status").fadeOut(800);
}

function saveValOpt(name, val) {
 if (config.saveprefs() && isStateSaved()) {
   storeVal(name, val);
 }
}

function saveJsonValOpt(name, val) {
 if (config.saveprefs() && isStateSaved()) {
   storeJsonVal(name, val);
 }
}

/* help buttons */
function toggleHelp(e) {
  $("#" + this.id + "-help").toggle();
}
$(".help-b").click(toggleHelp);

var map = L.map('map', {
      editable: true,
      editOptions: {
            lineGuideOptions: {
              color: "red",
              weight: 4,
              opacity: 0.5
            }
          }
    });
var track;
var metadata;
var waypoints;
var editLayer;
var route;
var routeStart;
var polystats;
var NEW_TRACK_NAME = "New Track";

var EDIT_NONE = 0;
var EDIT_MANUAL_TRACK = 1;
var EDIT_AUTO_TRACK = 2;
var EDIT_MARKER = 3;
var EDIT_DEFAULT = EDIT_MANUAL_TRACK;
var editMode = -1;


function setTrackName(name) {
  $("#track-name").text(name);
  document.title = config.appname + " - " + name;
  metadata.name = name;
}
function getTrackName() {
  return metadata.name;
}
function askTrackName() {
    var name = prompt("Track name:", getTrackName());
    if (name) {
      setTrackName(name);
    }
}

function validatePrompt() {
  setTrackName($("#prompt-name").val());
  metadata.desc = $("#prompt-desc").val();
  saveState();
  closeTrackNamePrompt();
}

function promptTrackName() {
  $("#prompt-name").val(getTrackName());
  $("#prompt-desc").val(metadata.desc);
  $("#prompt").show();
  $("#prompt-name").focus();
}
function closeTrackNamePrompt() {
  $("#prompt").hide();
}

function promptKeyEvent(event) {
  if ( event.which == 27 ) {
    closeTrackNamePrompt();
  } else if (event.keyCode == 13) {
    validatePrompt();
  }
}

$("#prompt-name").keyup(function promptKeyEvent(event) {
  if ( event.which == 27 ) {
    closeTrackNamePrompt();
  } else if (event.keyCode == 13) {
    validatePrompt();
  }
});
$("#prompt-desc").keyup(function promptKeyEvent(event) {
  if ( event.which == 27 ) {
    closeTrackNamePrompt();
  }
});

$("#prompt-ok").click(validatePrompt);
$("#prompt-cancel").click(closeTrackNamePrompt);

$("#track-name-edit").click(promptTrackName);


var selectActivity = $("#activity")[0];
var activities;

function loadActivities() {
  activities = getJsonVal("wt.activities");
  if (!activities) {
    activities = config.activities.defaults;
    saveJsonValOpt("wt.activities", activities);
  }
  // append activities
  for (var a in activities) {
    if (hasOwnProperty.call(activities, a)) {
      if (!selectActivity.options[a]) {
        addSelectOption(selectActivity, a);
      }
    }
  }
  // remove deleted activites
  $("#activity option").each(function(i,v) {
    if (!activities[v.innerHTML]) {
      v.remove();
    }
  });
}
loadActivities();
selectOption(selectActivity, getVal("wt.activity", undefined));

function getCurrentActivity() {
  var res = $("#activity").children(':selected').val();
  log("activity: " + res);
  saveValOpt("wt.activity", res);
  return activities[res];
}
$("#activity").click(loadActivities);
$("#activity").change(function() {
  polystats.setSpeedProfile(getCurrentActivity().speedprofile);
});

/* ------------------------------------------------------------*/

function newTrack() {
  metadata = {};
  if (track) {
    track.remove();
    track = undefined;
  }
  if (waypoints) {
    waypoints.remove();
  }
  if (editLayer) {
    editLayer.remove();
  }
  if (route) {
    route.remove();
    route = undefined;
  }
  routeStart = undefined;
  editLayer = L.layerGroup([]).addTo(map);
  waypoints = L.layerGroup([]).addTo(editLayer);
  track = L.polyline([]);
  track.setStyle({color:"#F00", dashColor:"#F00", });
  track.addTo(editLayer);
  polystats = L.Util.polyStats(track, {
    chrono: true,
    speedProfile:  getCurrentActivity().speedprofile,
    onUpdate: showStats,
  });
  setTrackName(NEW_TRACK_NAME);
//  setEditMode(EDIT_NONE);
  showStats();
}

function newWaypoint(latlng, name, desc) {

  function deletMarker(e) {
    marker.remove();
    map.closePopup();
    e.preventDefault();
  }

  function getMarkerPopupContent(marker) {
    var div = L.DomUtil.create('div', "popupdiv"),
      label, input;

    if (editMode === EDIT_MARKER) {

      // name
      label = L.DomUtil.create('div', "popupdiv", div);
      label.innerHTML = "<span class='popupfield'>Name:</span> ";
      var name = L.DomUtil.create('input', "popup-nameinput", label);
      name.type = "text";
      name.value = marker.options.title ? marker.options.title : "";
      name.onkeyup = function(){
        marker.options.title = name.value;
        var elt = marker.getElement();
        elt.title = name.value;
        elt.alt = name.value;
      };

      // description
      label = L.DomUtil.create('div', "popupdiv", div);
      label.innerHTML = "<span class='popupfield'>Desc:</span> ";
      var desc = L.DomUtil.create('textarea', "popup-descinput", label);
      desc.value = marker.options.desc ? marker.options.desc : "";
      desc.onkeyup = function(){
        marker.options.desc = desc.value;
      };

    } else {

      // name
      if (marker.options.title) {
        var popupName = L.DomUtil.create('div', "popup-name", div);
        popupName.innerHTML = marker.options.title;
      }

      // description
      if (marker.options.desc) {
        var popupDesc = L.DomUtil.create('div', "popup-desc", div);
        popupDesc.innerHTML = marker.options.desc;
      }
    }



    var latlng = marker.getLatLng();
    var markerDiv = getLatLngPopupContent(latlng, deletMarker, div);
    return markerDiv;
  }

  var markerIcon = L.icon({
    iconUrl: 'img/marker-icon.png',
    iconRetinaUrl: 'img/marker-icon-2x.png',
    iconSize: [16, 26],
    iconAnchor: [8, 26],
    popupAnchor: [0, 0],
    shadowUrl: 'img/marker-shadow.png',
    shadowRetinaUrl: 'img/marker-shadow-2x.png',
    shadowSize: [26, 26],
    shadowAnchor: [8, 26]
  });
  var marker = L.marker(latlng, {
    title: name,
    desc: desc,
    alt: name,
    icon: markerIcon
  }).addTo(waypoints);

  marker.on("click", function() {
    pop = L.popup()
        .setLatLng(marker.getLatLng())
        .setContent(getMarkerPopupContent(marker))
        .openOn(map);
  });

  return marker;
}

/* ------------------------ TRIMMING ---------------------------------- */

var polytrim;
function prepareTrim() {
  var trimMax = Math.round(track.getLatLngs().length / 2);
  $("#trim-txt").text("");
  $("#trim-range").attr("max", trimMax);
  $("#trim-range").val(0);
  $(".no-trim").prop('disabled',false);
  var trimType = $("#trim-type")[0].selectedIndex;
  polytrim = L.Util.polyTrim(track, trimType);
}

function trimTrack(e) {
  var n = parseInt($("#trim-range").val());
  log("trimming " + n);
  $("#trim-txt").text(n + "/" + polytrim.getPolySize());
  $(".no-trim").prop('disabled',(n !== 0));
  polytrim.trim(n);
}

function finishTrim() {
  if (polytrim.getDirection() === polytrim.FROM_END) {
    // From End
    polystats.updateStatsFrom(track.getLatLngs().length-1);
  } else {
    // From Start
    polystats.updateStatsFrom(0);
  }
  polytrim = undefined;
  saveState();
}

$("#trim-range").on("change", trimTrack);
$("#trim-type").change(prepareTrim);

/* ------------------------ MENU ---------------------------------- */

$("#menu-button").click(function() {
  if (!$("#menu").is(":visible")) {
    setEditMode(EDIT_NONE);
    setChecked("#merge", false);
    menu("file");
    $("#menu").show();
    prepareTrim();
  } else {
    $("#menu").hide();
    finishTrim();
  }
  return false;
});
$("#menu-close").click(function() {
  $("#menu").hide();
  finishTrim();
  return false;
});
$("#track-new").click(function() {
  newTrack();
  setEditMode(EDIT_MANUAL_TRACK);
  saveState();
});
$("#menu-track").click(function() {
  $(".collapsable-track").toggle();
});
$("#menu-tools").click(function() {
  $(".collapsable-tools").toggle();
});

/* ------------------------ EXPORT GPX ---------------------------------- */

function LatLngToGPX(latlng, gpxelt, name, time, desc) {

  var gpx = "<"+gpxelt;
  gpx += " lat=\"" + latlng.lat+ "\" lon=\"" + latlng.lng + "\">";
  if (name) {
    gpx += "<name>" + htmlEncode(name, false, 0)  + "</name>";
  }
  if (desc) {
    gpx += "<desc>" + htmlEncode(desc, false, 0)  + "</desc>";
  }
  if (latlng.alt) {
    gpx += "<ele>" + latlng.alt + "</ele>";
  }
  if (time) {
    gpx += "<time>" + (typeof time === "string" ? time : time.toISOString()) + "</time>";
  }
  gpx += "</"+gpxelt+">\n";
  return gpx;
}

function getGPX(trackname, savealt, savetime, asroute, nometadata) {

  var now = new Date();
  var gpx = '<\?xml version="1.0" encoding="UTF-8" standalone="no" \?>\n';
  gpx += '<gpx creator="' + config.appname + '" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.topografix.com/GPX/1/1" version="1.1" xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd">\n';
  if (!nometadata) {
    gpx += "<metadata>\n";
    gpx += "  <name>" + trackname + "</name>\n";
    gpx += "  <desc>" + (metadata.desc ? metadata.desc : "") + "</desc>\n";
    gpx += "  <author><name>" + config.appname + "</name></author>\n";
    gpx += "  <link href='" + window.location.href + "'>\n";
    gpx += "    <text>" + config.appname + "</text>\n";
    gpx += "    <type>text/html</type>\n";
    gpx += "  </link>\n";
    gpx += "  <time>" + now.toISOString() + "</time>\n";
    var sw = map.getBounds().getSouthWest();
    var ne = map.getBounds().getNorthEast();
    gpx += '  <bounds minlat="' + Math.min(sw.lat, ne.lat) + '" minlon="' + Math.min(sw.lng, ne.lng) + '" maxlat="' + Math.max(sw.lat, ne.lat) + '" maxlon="'+ Math.max(sw.lng, ne.lng) + '"/>\n';
    gpx += "</metadata>\n";
  }
  var wpts = waypoints ? waypoints.getLayers() : undefined;
  if (wpts && wpts.length > 0) {
    var i = 0;
    while (i < wpts.length) {
      var wpt = wpts[i];
      gpx += LatLngToGPX(wpt.getLatLng(), "wpt", wpt.options.title, wpt.getLatLng().time, wpt.options.desc);
      i++;
    }
  }
  var latlngs = track ? track.getLatLngs() : undefined;
  if (latlngs && latlngs.length > 0) {
    var xmlname = "<name>" + trackname + "</name>";
    var ptindent = "  ";
    var pttag;
    if (asroute) {
      gpx += "<rte>" + xmlname + "\n";
      pttag = "rtept";
    } else {
      gpx += "<trk>" + xmlname + "\n  <trkseg>\n";
      ptindent += "  ";
      pttag = "trkpt";
    }
    var j = 0;
    var saveTiming = isChecked("#savetiming");
    now = now.getTime();
    while (j < latlngs.length) {
      var pt = latlngs[j];
      var time;
      if (saveTiming) {
        time = new Date(now + (pt.chrono*1000));
      } else {
        time = pt.time;
      }
      gpx += ptindent + LatLngToGPX(pt, pttag, undefined, time);
      j++;
    }
    if (asroute) {
      gpx += "</rte>\n";
    } else {
      gpx += "  </trkseg>\n</trk>\n";
    }
  }
  gpx += "</gpx>\n";
  return gpx;
}

$("#track-download").click(function() {
  setEditMode(EDIT_NONE);
  setStatus("Formatting..", {spinner: true});
  var asroute = isChecked("#as-route");
  var nometadata = isChecked("#nometadata");
  var trackname =  getTrackName();
  if (trackname === NEW_TRACK_NAME) {
    askTrackName();
    trackname =  getTrackName();
  }
  var gpx = getGPX(trackname, /*savealt*/false, /*savetime*/false, asroute, nometadata);
  if (isSafari()) alert("A new page will open, press cmd+s (" + String.fromCharCode(8984) + "+s) to save file");
  var blob = new Blob([gpx],
    isSafari() ? {type: "text/plain;charset=utf-8"} :
      {type: "application/gpx+xml;charset=utf-8"}
  );
  saveAs(blob, trackname+".gpx");
  clearStatus();
});

function editableWaypoints(editable) {
  var wpts = waypoints.getLayers();
  for (var i=0; i< wpts.length; i++) {
    if (editable) {
      wpts[i].enableEdit();
    } else {
      wpts[i].disableEdit();
    }
  }
}

function mergeRouteToTrack() {
  if (!route) return;
  var initlen = track.getLatLngs().length;
  var pts = route._selectedRoute ? route._selectedRoute.coordinates : [];
  pts = L.PolyUtil.prune(pts, config.compressdefault);
  route.remove();
  route = undefined;
  routeStart = undefined;
  for (var j = 0; j < pts.length; j++) {
    track.addLatLng(pts[j]);
  }
  elevate(pts, function() {
    polystats.updateStatsFrom(initlen);
  });
}

function setRouteStart(latlng) {
  routeStart = latlng;
  $("#map").css("cursor", "alias");
}

function closeOverlays() {
  // close all
  $("#menu").hide();
  map.closePopup();
  hideElevation();
}

function restartRoute() {
  if (route) {
    route.remove();
    route = undefined;
    routeStart = undefined;
  }
  $("#map").css("cursor", "copy");
  var ll = track.getLatLngs();
  if (ll.length > 0) {
    setRouteStart(ll[ll.length-1]);
  }
}

function showGraphHopperCredit() {
  $("#map").append("<span class='gh-credit'>Powered by <a href='https://graphhopper.com/#directions-api'>GraphHopper API</a></span>");
}
function hideGraphHopperCredit() {
  $(".gh-credit").remove();
}
function showGraphHopperMessage(msg) {
  setStatus(msg, {timeout:5, class:"status-error"});
}

function setEditMode(mode) {
  closeOverlays();
  if (mode === editMode) {
    return;
  }
  switch (editMode) {
    case EDIT_MANUAL_TRACK:
      if (track) {
        track.disableEdit();
      }
      break;
    case EDIT_AUTO_TRACK:
      hideGraphHopperCredit();
      mergeRouteToTrack();
      break;
    case EDIT_MARKER:
      editableWaypoints(false);
      break;
    default:
  }
  map.editTools.stopDrawing();
  $("#edit-tools a").removeClass("control-selected");
  if (editMode > 0) { // exiting edit mode
    saveState();
    // reset mouse pointer
    $("#map").css("cursor", "");
  }
  switch (mode) {
    case EDIT_NONE:
      break;
    case EDIT_MANUAL_TRACK:
      $("#edit-manual").addClass("control-selected");
      track.enableEdit();
      track.editor.continueForward();
      break;
    case EDIT_AUTO_TRACK:
      $("#edit-auto").addClass("control-selected");
      showGraphHopperCredit();
      restartRoute();
      break;
    case EDIT_MARKER:
      $("#edit-marker").addClass("control-selected");
      $("#map").css("cursor", "url(img/marker-icon.png) 7 25,text");
      editableWaypoints(true);
      break;
    default:
      error("invalid edit mode: " + mode);
      return;
  }
  editMode = mode;
  $("#edit-tools").toggle(editMode > 0);
}

$("#compress").click(function() {
  // get & check input value
  var prunedist = $("#prunedist");
  var input = prunedist.val().trim();
  var tolerance;
  if (input) {
    tolerance = parseFloat(input);
  }
  if ((tolerance === undefined) || isNaN(tolerance)) {
    alert("Enter distance in meters");
    prunedist.focus();
    return;
  }

  if (track) {
    setEditMode(EDIT_NONE);
    var pts = track.getLatLngs();
    var pruned = L.PolyUtil.prune(pts, tolerance);
    var removedpts = (pts.length - pruned.length);
    if (removedpts> 0) {
      alert("Removed " + removedpts + " points out of " + pts.length + " (" + Math.round((removedpts / pts.length) * 100) + "%)");
      // switch to new values
      track.setLatLngs(pruned);
      saveState();
    } else {
      setStatus("Already optimized", {timeout:3});
    }
  }
});

function getMyIpLocation() {
  log("Getting location from IP address");
  var geoapi = "https://freegeoip.net/json/?callback=";
  $.getScript(geoapi+"setMyIpLocation")
  .fail(function( jqxhr, settings, exception ) {
    warn("freegeoip request failed");
  });
}

function setMyIpLocation(res) {
  setLocation({
    lat: res.latitude,
    lng: res.longitude
  }, false);
}

var myLocIcon = L.icon({
    iconUrl: 'img/mylocation.png',
    iconSize:     [48, 48],
    iconAnchor:   [24, 24]
});
var myLocMarker;
var myLocTimer;
var
  LOC_NONE = 0,
  LOC_ONCE = 1,
  LOC_CONTINUOUS = 2,
  showLocation = LOC_NONE;

function removeMyLocMarker() {
  if (showLocation == LOC_CONTINUOUS) {
    gotoMyLocation();
  } else {
    showLocation = LOC_NONE;
    if (myLocMarker) {
      myLocMarker.remove();
      myLocMarker = undefined;
    }
  }
}

function setLocation(pos, showIcon) {
  if (showLocation == LOC_NONE) return; // cancelled in the meantime
  var zoom = map.getZoom() ? map.getZoom() : config.display.zoom;
  map.setView(pos, zoom);
  if (myLocMarker) {
    clearTimeout(myLocTimer);
    myLocMarker.remove();
  }
  if (showIcon) {
    myLocMarker = new L.marker([ pos.lat, pos.lng ], {icon: myLocIcon, clickable:false});
    myLocMarker.addTo(map);
  }
  if (showIcon || (showLocation == LOC_CONTINUOUS)) {
    myLocTimer = setTimeout(removeMyLocMarker, 5000);
  } else {
    showLocation = LOC_NONE;
  }
}

function gotoMyLocation() {

  function gotLocation(position) {
    log("Got location");
    setLocation({
      lat: position.coords.latitude,
      lng: position.coords.longitude
    }, true);
  }

  function highAccuracyFailed(posError) {
    log("GPS location failed, trying low accuracy");
    navigator.geolocation.getCurrentPosition(
             gotLocation, lowAccuracyFailed,
             {maximumAge:60000, timeout:5000, enableHighAccuracy: false});
  }

  function lowAccuracyFailed(posError) {
    log("low accuracy geolococation failed");
    getMyIpLocation();
  }


  if (navigator.geolocation) {
    navigator.geolocation.getCurrentPosition(
      gotLocation, highAccuracyFailed,
       {maximumAge:0, timeout:5000, enableHighAccuracy: true});
  } else {
    log("no runtime geolococation available");
    getMyIpLocation();
  }
}

function getSavedPosition(_lat, _lng) {
  var vlat = getVal("wt.poslat", _lat);
  var vlng = getVal("wt.poslng", _lng);
  var pos = {
    lat: parseFloat(vlat),
    lng: parseFloat(vlng)
  };
  // ask for position on first use
  if (vlat == _lat && vlng == _lng) {
    setTimeout(function() {
      showLocation = LOC_ONCE;
      gotoMyLocation();
    }, 1000);
  }
  return pos;
}

function savePosition() {
  var pos = map.getCenter();
  saveValOpt("wt.poslat",pos.lat);
  saveValOpt("wt.poslng",pos.lng);
}

function saveEditMode() {
  if (editMode >= 0) saveValOpt("wt.editMode",editMode);
}

function saveTrack() {
  var trackname =  getTrackName();
  var numPts = track.getLatLngs().length + waypoints.getLayers().length;
  if (numPts < 1000) {
    var gpx = getGPX(trackname, /*savealt*/false, /*savetime*/false, /*asroute*/false, /*nometadata*/false);
    saveValOpt("wt.gpx",gpx);
  }
}

function saveState() {
  if (isStateSaved()) {
    saveTrack();
    savePosition();
    saveEditMode();
  }
}

function restoreEditMode() {
  var restoredMode = getVal("wt.editMode", EDIT_DEFAULT);
  setEditMode(parseInt(restoredMode));
}

function restorePosition() {
  var defpos = getSavedPosition(config.display.pos.lat, config.display.pos.lng);
  showLocation = LOC_ONCE;
  setLocation(defpos); // required to initialize map
}

function restoreTrack() {
  var gpx = getVal("wt.gpx", null);
  if (gpx) {
    loadCount = 0;
    fileloader.loadData(gpx, "dummy", "gpx");
    return true;
  } else {
    newTrack();
    return false;
  }
}

function saveInfo(save) {
  $("#save-info").hide();
  setChecked("#cfgsave", save);
  $("#cfgsave").change();
}
$("#save-yes").click(function (){
  saveInfo(true);
});
$("#save-no").click(function (){
  saveInfo(false);
});

function restoreState() {
  var isSaving = getVal("wt.saveState", null);
  if (isUnset(isSaving)) {
    $("#save-info").show();
  } else {
    setChecked("#cfgsave", isSaving == "true");
  }
  if (!restoreTrack()) {
    restorePosition();
  }
  restoreEditMode();
}

function clearSavedState() {
  storeVal("wt.gpx", undefined);
  storeVal("wt.poslat", undefined);
  storeVal("wt.poslng", undefined);
  storeVal("wt.editMode", undefined);
  storeVal("wt.baseLayer", undefined);
  storeVal("wt.overlays", undefined);
  storeVal("wt.activity", undefined);
  storeVal("wt.activities", undefined);
}

function saveMapType() {
  saveValOpt("wt.maptype", map.getMapTypeId());
}

function getProvider(name) {
  var p;
  if (name == "opentopomap") {
    p =  L.tileLayer('https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png', {
      maxZoom: 17,
      attribution: 'Map data: &copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>, <a href="https://viewfinderpanoramas.org">SRTM</a> | Map style: &copy; <a href="https://opentopomap.org">OpenTopoMap</a> (<a href="https://creativecommons.org/licenses/by-sa/3.0/">CC-BY-SA</a>)'
    });
  } else if (name == "osm:std") {
    p = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
      maxZoom: 19,
      attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>'
    });
  } else if (name == "osm:hot") {
    p = L.tileLayer('https://{s}.tile.openstreetmap.fr/hot/{z}/{x}/{y}.png', {
      maxZoom: 19,
      attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>'
    });
  } else if (name == "tf:cycle") {
    p = L.tileLayer('https://{s}.tile.thunderforest.com/cycle/{z}/{x}/{y}.png?apikey='+config.thunderforest.key(), {
      maxZoom: 18,
      attribution: '&copy; <a href="https://www.thunderforest.com/">Thunderforest</a> &copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>'
    });

  } else if (name == "sigma:cycle") {
    p = L.tileLayer('https://tiles1.sigma-dc-control.com/layer5/{z}/{x}/{y}.png', {
      maxZoom: 18,
      attribution: '&copy; <a href="http://www.sigmasport.com/" target="_blank">SIGMA Sport &reg;</a> Map data <a href="http://www.openstreetmap.org/" target="_blank">OpenStreetMap</a>'
    });
  } else if (name == "tf:outdoors") {
    p = L.tileLayer('https://{s}.tile.thunderforest.com/outdoors/{z}/{x}/{y}.png?apikey='+config.thunderforest.key(), {
      maxZoom: 18,
      attribution: '&copy; <a href="https://www.thunderforest.com/">Thunderforest</a>, &copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>'
    });
  } else if (name == "wmf:hikebike") {
    //p = L.tileLayer('http://{s}.tiles.wmflabs.org/hikebike/{z}/{x}/{y}.png', {
    p = L.tileLayer('https://tiles.wmflabs.org/hikebike/{z}/{x}/{y}.png', {
      maxZoom: 17,
      attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>'
    });
  }  else if (name == "esri:worldtopomap") {
    p = L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer/tile/{z}/{y}/{x}', {
      attribution: 'Tiles &copy; Esri &mdash; Esri and GIS Community'
    });
  }  else if (name == "esri:worldstreetmap") {
    p = L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer/tile/{z}/{y}/{x}', {
      attribution: 'Tiles &copy; Esri &mdash; Esri &amp; al.'
    });
  }  else if (name == "mtbmap") {
    p = L.tileLayer('http://tile.mtbmap.cz/mtbmap_tiles/{z}/{x}/{y}.png', {
      attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> &amp; USGS'
    });
  }  else if (name == "map1.eu") {
    p = L.tileLayer('http://beta.map1.eu/tiles/{z}/{x}/{y}.jpg', {
      maxZoom: 17,
      attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> &amp; <a href="http://map1.eu">map1.eu</a>'
    });
  } else if (name == 'google:roadmap') {
    p = L.tileLayer('https://{s}.google.com/vt/lyrs=m&x={x}&y={y}&z={z}',{
        maxZoom: 20,
        subdomains:['mt0','mt1','mt2','mt3'],
        attribution: '&copy; Google'
    });
  } else if (name == 'google:terrain') {
    p = L.tileLayer('https://{s}.google.com/vt/lyrs=p&x={x}&y={y}&z={z}',{
        maxZoom: 20,
        subdomains:['mt0','mt1','mt2','mt3'],
        attribution: '&copy; Google'
    });
  } else if (name == 'google:satellite') {
    p = L.tileLayer('https://{s}.google.com/vt/lyrs=s&x={x}&y={y}&z={z}',{
        maxZoom: 20,
        subdomains:['mt0','mt1','mt2','mt3'],
        attribution: '&copy; Google'
    });
  } else if (name == 'google:hybrid') {
    p = L.tileLayer('https://{s}.google.com/vt/lyrs=s,h&x={x}&y={y}&z={z}',{
        maxZoom: 20,
        subdomains:['mt0','mt1','mt2','mt3'],
        attribution: '&copy; Google'
    });
  } else if (name == 'wmf:hills') {
    //p = L.tileLayer('http://{s}.tiles.wmflabs.org/hillshading/{z}/{x}/{y}.png',{
    p = L.tileLayer('https://tiles.wmflabs.org/hillshading/{z}/{x}/{y}.png',{
      maxZoom: 17,
      attribution: 'Hillshading: SRTM3 v2 (<a href="https://www2.jpl.nasa.gov/srtm/">NASA</a>)'
    });
  } else if (name == 'lv:hike') {
    p = L.tileLayer('http://tile.waymarkedtrails.org/hiking/{z}/{x}/{y}.png',{
        maxZoom: 17,
      attribution: 'Hiking Routes: (<a href="http://hiking.lonvia.de">Lonvias Hiking Map</a>)'
    });
  } else if (name == 'lv:bike') {
    p = L.tileLayer('http://tile.waymarkedtrails.org/cycling/{z}/{x}/{y}.png',{
        maxZoom: 17,
      attribution: 'Cycling Routes: (<a href="http://cycling.lonvia.de">Lonvias Cycling Map</a>)'
    });
  } else if (name == 'fr:ignclassic') {
    p = L.tileLayer.wtms(
          "https://wxs.ign.fr/" + config.ign.key() + "/geoportail/wmts",
          {
            layer: 'GEOGRAPHICALGRIDSYSTEMS.MAPS',
            style: 'normal',
            tilematrixSet: "PM",
            format: 'image/jpeg',
            attribution: "&copy; <a href='http://www.ign.fr'>IGN</a>"
          });
  } else if (name == 'fr:ignexpress') {
    p = L.tileLayer.wtms(
          "https://wxs.ign.fr/" + config.ign.key() + "/geoportail/wmts",
          {
            layer: 'GEOGRAPHICALGRIDSYSTEMS.MAPS.SCAN-EXPRESS.STANDARD',
            style: 'normal',
            tilematrixSet: "PM",
            format: 'image/jpeg',
            attribution: "&copy; <a href='http://www.ign.fr'>IGN</a>"
          });
  } else if (name == 'fr:ignimagery') {
    p = L.tileLayer.wtms(
          "https://wxs.ign.fr/" + config.ign.key() + "/geoportail/wmts",
          {
            layer: 'ORTHOIMAGERY.ORTHOPHOTOS',
            style: 'normal',
            tilematrixSet: "PM",
            format: 'image/jpeg',
            attribution: "&copy; <a href='http://www.ign.fr'>IGN</a>"
          });
  } else if (name == 'spain:ignraster') {
    // https://github.com/sigdeletras/Leaflet.Spain.WMS
    p = L.tileLayer.wms('https://www.ign.es/wms-inspire/mapa-raster', {
        	layers: 'mtn_rasterizado',
        	format: 'image/png',
        	transparent: false,
        	continuousWorld : true,
        	attribution: '© <a href="http://www.ign.es/ign/main/index.do" target="_blank">Instituto Geográfico Nacional de España</a>'
        });
  }
  if (!p) {
    p = getProvider("osm:std");
  }
  return p;
}

var baseLayers = {
  "Open Topo": getProvider("opentopomap"),
  "OpenStreetMap": getProvider("osm:std"),
  "OpenCycleMap": getProvider("tf:cycle"),
  "Outdoors": getProvider("tf:outdoors"),
  "Sigma Cycle": getProvider("sigma:cycle"),
  "OSM HOT": getProvider("osm:hot"),
  "OSM HikeBike": getProvider("wmf:hikebike"),
  "ESRI Topo": getProvider("esri:worldtopomap"),
  "ESRI Street": getProvider("esri:worldstreetmap"),
  "MTB (*)": getProvider("mtbmap"),
  "Map1.eu (*)": getProvider("map1.eu"),
  "Google roads": getProvider('google:roadmap'),
  "Google Terrain": getProvider('google:terrain'),
  "Google Satellite": getProvider('google:satellite'),
  "Google Hybrid": getProvider('google:hybrid'),
  "FR IGN classic": getProvider("fr:ignclassic"),
  "FR IGN express": getProvider("fr:ignexpress"),
  //"FR IGN aerial": getProvider("fr:ignimagery")
  "ES IGN": getProvider("spain:ignraster"),
};
var overlays = {
  "Hillshading": getProvider("wmf:hills"),
  "Hiking Routes (*)": getProvider("lv:hike"),
  "Cycling Routes (*)": getProvider("lv:bike"),
};

L.control.layers(baseLayers, overlays).addTo(map);
map.addLayer(baseLayers[getVal("wt.baseLayer", config.display.map)] || baseLayers[config.display.map]);
map.on("baselayerchange", function(e) {
  saveValOpt("wt.baseLayer", e.name);
  $(".leaflet-control-layers").removeClass("leaflet-control-layers-expanded");
});
function getOverlays() {
  var v = getJsonVal("wt.overlays");
  return v || {};
}

function setOverlay(name, yesno) {
  var cfg = getOverlays();
  cfg[name] = yesno;
  saveJsonValOpt("wt.overlays", cfg);
}
<commit_msg>Added Catalan's ICGC map<commit_after>function setStatus(msg, options) {
  $("#status-msg").text(msg);
  var statusclass = options && options.class ? options.class : "status-info";
  $("#status-msg").attr("class", statusclass);
  var showspinner = options && !isUnset(options.spinner) && options.spinner;
  $("#spinner").toggle(showspinner);
  $("#status").fadeIn();
  if (options && options.timeout) {
    setTimeout(function(){ clearStatus(); }, 1000*options.timeout);
  }
}

function clearStatus() {
  $("#status").fadeOut(800);
}

function saveValOpt(name, val) {
 if (config.saveprefs() && isStateSaved()) {
   storeVal(name, val);
 }
}

function saveJsonValOpt(name, val) {
 if (config.saveprefs() && isStateSaved()) {
   storeJsonVal(name, val);
 }
}

/* help buttons */
function toggleHelp(e) {
  $("#" + this.id + "-help").toggle();
}
$(".help-b").click(toggleHelp);

var map = L.map('map', {
      editable: true,
      editOptions: {
            lineGuideOptions: {
              color: "red",
              weight: 4,
              opacity: 0.5
            }
          }
    });
var track;
var metadata;
var waypoints;
var editLayer;
var route;
var routeStart;
var polystats;
var NEW_TRACK_NAME = "New Track";

var EDIT_NONE = 0;
var EDIT_MANUAL_TRACK = 1;
var EDIT_AUTO_TRACK = 2;
var EDIT_MARKER = 3;
var EDIT_DEFAULT = EDIT_MANUAL_TRACK;
var editMode = -1;


function setTrackName(name) {
  $("#track-name").text(name);
  document.title = config.appname + " - " + name;
  metadata.name = name;
}
function getTrackName() {
  return metadata.name;
}
function askTrackName() {
    var name = prompt("Track name:", getTrackName());
    if (name) {
      setTrackName(name);
    }
}

function validatePrompt() {
  setTrackName($("#prompt-name").val());
  metadata.desc = $("#prompt-desc").val();
  saveState();
  closeTrackNamePrompt();
}

function promptTrackName() {
  $("#prompt-name").val(getTrackName());
  $("#prompt-desc").val(metadata.desc);
  $("#prompt").show();
  $("#prompt-name").focus();
}
function closeTrackNamePrompt() {
  $("#prompt").hide();
}

function promptKeyEvent(event) {
  if ( event.which == 27 ) {
    closeTrackNamePrompt();
  } else if (event.keyCode == 13) {
    validatePrompt();
  }
}

$("#prompt-name").keyup(function promptKeyEvent(event) {
  if ( event.which == 27 ) {
    closeTrackNamePrompt();
  } else if (event.keyCode == 13) {
    validatePrompt();
  }
});
$("#prompt-desc").keyup(function promptKeyEvent(event) {
  if ( event.which == 27 ) {
    closeTrackNamePrompt();
  }
});

$("#prompt-ok").click(validatePrompt);
$("#prompt-cancel").click(closeTrackNamePrompt);

$("#track-name-edit").click(promptTrackName);


var selectActivity = $("#activity")[0];
var activities;

function loadActivities() {
  activities = getJsonVal("wt.activities");
  if (!activities) {
    activities = config.activities.defaults;
    saveJsonValOpt("wt.activities", activities);
  }
  // append activities
  for (var a in activities) {
    if (hasOwnProperty.call(activities, a)) {
      if (!selectActivity.options[a]) {
        addSelectOption(selectActivity, a);
      }
    }
  }
  // remove deleted activites
  $("#activity option").each(function(i,v) {
    if (!activities[v.innerHTML]) {
      v.remove();
    }
  });
}
loadActivities();
selectOption(selectActivity, getVal("wt.activity", undefined));

function getCurrentActivity() {
  var res = $("#activity").children(':selected').val();
  log("activity: " + res);
  saveValOpt("wt.activity", res);
  return activities[res];
}
$("#activity").click(loadActivities);
$("#activity").change(function() {
  polystats.setSpeedProfile(getCurrentActivity().speedprofile);
});

/* ------------------------------------------------------------*/

function newTrack() {
  metadata = {};
  if (track) {
    track.remove();
    track = undefined;
  }
  if (waypoints) {
    waypoints.remove();
  }
  if (editLayer) {
    editLayer.remove();
  }
  if (route) {
    route.remove();
    route = undefined;
  }
  routeStart = undefined;
  editLayer = L.layerGroup([]).addTo(map);
  waypoints = L.layerGroup([]).addTo(editLayer);
  track = L.polyline([]);
  track.setStyle({color:"#F00", dashColor:"#F00", });
  track.addTo(editLayer);
  polystats = L.Util.polyStats(track, {
    chrono: true,
    speedProfile:  getCurrentActivity().speedprofile,
    onUpdate: showStats,
  });
  setTrackName(NEW_TRACK_NAME);
//  setEditMode(EDIT_NONE);
  showStats();
}

function newWaypoint(latlng, name, desc) {

  function deletMarker(e) {
    marker.remove();
    map.closePopup();
    e.preventDefault();
  }

  function getMarkerPopupContent(marker) {
    var div = L.DomUtil.create('div', "popupdiv"),
      label, input;

    if (editMode === EDIT_MARKER) {

      // name
      label = L.DomUtil.create('div', "popupdiv", div);
      label.innerHTML = "<span class='popupfield'>Name:</span> ";
      var name = L.DomUtil.create('input', "popup-nameinput", label);
      name.type = "text";
      name.value = marker.options.title ? marker.options.title : "";
      name.onkeyup = function(){
        marker.options.title = name.value;
        var elt = marker.getElement();
        elt.title = name.value;
        elt.alt = name.value;
      };

      // description
      label = L.DomUtil.create('div', "popupdiv", div);
      label.innerHTML = "<span class='popupfield'>Desc:</span> ";
      var desc = L.DomUtil.create('textarea', "popup-descinput", label);
      desc.value = marker.options.desc ? marker.options.desc : "";
      desc.onkeyup = function(){
        marker.options.desc = desc.value;
      };

    } else {

      // name
      if (marker.options.title) {
        var popupName = L.DomUtil.create('div', "popup-name", div);
        popupName.innerHTML = marker.options.title;
      }

      // description
      if (marker.options.desc) {
        var popupDesc = L.DomUtil.create('div', "popup-desc", div);
        popupDesc.innerHTML = marker.options.desc;
      }
    }



    var latlng = marker.getLatLng();
    var markerDiv = getLatLngPopupContent(latlng, deletMarker, div);
    return markerDiv;
  }

  var markerIcon = L.icon({
    iconUrl: 'img/marker-icon.png',
    iconRetinaUrl: 'img/marker-icon-2x.png',
    iconSize: [16, 26],
    iconAnchor: [8, 26],
    popupAnchor: [0, 0],
    shadowUrl: 'img/marker-shadow.png',
    shadowRetinaUrl: 'img/marker-shadow-2x.png',
    shadowSize: [26, 26],
    shadowAnchor: [8, 26]
  });
  var marker = L.marker(latlng, {
    title: name,
    desc: desc,
    alt: name,
    icon: markerIcon
  }).addTo(waypoints);

  marker.on("click", function() {
    pop = L.popup()
        .setLatLng(marker.getLatLng())
        .setContent(getMarkerPopupContent(marker))
        .openOn(map);
  });

  return marker;
}

/* ------------------------ TRIMMING ---------------------------------- */

var polytrim;
function prepareTrim() {
  var trimMax = Math.round(track.getLatLngs().length / 2);
  $("#trim-txt").text("");
  $("#trim-range").attr("max", trimMax);
  $("#trim-range").val(0);
  $(".no-trim").prop('disabled',false);
  var trimType = $("#trim-type")[0].selectedIndex;
  polytrim = L.Util.polyTrim(track, trimType);
}

function trimTrack(e) {
  var n = parseInt($("#trim-range").val());
  log("trimming " + n);
  $("#trim-txt").text(n + "/" + polytrim.getPolySize());
  $(".no-trim").prop('disabled',(n !== 0));
  polytrim.trim(n);
}

function finishTrim() {
  if (polytrim.getDirection() === polytrim.FROM_END) {
    // From End
    polystats.updateStatsFrom(track.getLatLngs().length-1);
  } else {
    // From Start
    polystats.updateStatsFrom(0);
  }
  polytrim = undefined;
  saveState();
}

$("#trim-range").on("change", trimTrack);
$("#trim-type").change(prepareTrim);

/* ------------------------ MENU ---------------------------------- */

$("#menu-button").click(function() {
  if (!$("#menu").is(":visible")) {
    setEditMode(EDIT_NONE);
    setChecked("#merge", false);
    menu("file");
    $("#menu").show();
    prepareTrim();
  } else {
    $("#menu").hide();
    finishTrim();
  }
  return false;
});
$("#menu-close").click(function() {
  $("#menu").hide();
  finishTrim();
  return false;
});
$("#track-new").click(function() {
  newTrack();
  setEditMode(EDIT_MANUAL_TRACK);
  saveState();
});
$("#menu-track").click(function() {
  $(".collapsable-track").toggle();
});
$("#menu-tools").click(function() {
  $(".collapsable-tools").toggle();
});

/* ------------------------ EXPORT GPX ---------------------------------- */

function LatLngToGPX(latlng, gpxelt, name, time, desc) {

  var gpx = "<"+gpxelt;
  gpx += " lat=\"" + latlng.lat+ "\" lon=\"" + latlng.lng + "\">";
  if (name) {
    gpx += "<name>" + htmlEncode(name, false, 0)  + "</name>";
  }
  if (desc) {
    gpx += "<desc>" + htmlEncode(desc, false, 0)  + "</desc>";
  }
  if (latlng.alt) {
    gpx += "<ele>" + latlng.alt + "</ele>";
  }
  if (time) {
    gpx += "<time>" + (typeof time === "string" ? time : time.toISOString()) + "</time>";
  }
  gpx += "</"+gpxelt+">\n";
  return gpx;
}

function getGPX(trackname, savealt, savetime, asroute, nometadata) {

  var now = new Date();
  var gpx = '<\?xml version="1.0" encoding="UTF-8" standalone="no" \?>\n';
  gpx += '<gpx creator="' + config.appname + '" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.topografix.com/GPX/1/1" version="1.1" xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd">\n';
  if (!nometadata) {
    gpx += "<metadata>\n";
    gpx += "  <name>" + trackname + "</name>\n";
    gpx += "  <desc>" + (metadata.desc ? metadata.desc : "") + "</desc>\n";
    gpx += "  <author><name>" + config.appname + "</name></author>\n";
    gpx += "  <link href='" + window.location.href + "'>\n";
    gpx += "    <text>" + config.appname + "</text>\n";
    gpx += "    <type>text/html</type>\n";
    gpx += "  </link>\n";
    gpx += "  <time>" + now.toISOString() + "</time>\n";
    var sw = map.getBounds().getSouthWest();
    var ne = map.getBounds().getNorthEast();
    gpx += '  <bounds minlat="' + Math.min(sw.lat, ne.lat) + '" minlon="' + Math.min(sw.lng, ne.lng) + '" maxlat="' + Math.max(sw.lat, ne.lat) + '" maxlon="'+ Math.max(sw.lng, ne.lng) + '"/>\n';
    gpx += "</metadata>\n";
  }
  var wpts = waypoints ? waypoints.getLayers() : undefined;
  if (wpts && wpts.length > 0) {
    var i = 0;
    while (i < wpts.length) {
      var wpt = wpts[i];
      gpx += LatLngToGPX(wpt.getLatLng(), "wpt", wpt.options.title, wpt.getLatLng().time, wpt.options.desc);
      i++;
    }
  }
  var latlngs = track ? track.getLatLngs() : undefined;
  if (latlngs && latlngs.length > 0) {
    var xmlname = "<name>" + trackname + "</name>";
    var ptindent = "  ";
    var pttag;
    if (asroute) {
      gpx += "<rte>" + xmlname + "\n";
      pttag = "rtept";
    } else {
      gpx += "<trk>" + xmlname + "\n  <trkseg>\n";
      ptindent += "  ";
      pttag = "trkpt";
    }
    var j = 0;
    var saveTiming = isChecked("#savetiming");
    now = now.getTime();
    while (j < latlngs.length) {
      var pt = latlngs[j];
      var time;
      if (saveTiming) {
        time = new Date(now + (pt.chrono*1000));
      } else {
        time = pt.time;
      }
      gpx += ptindent + LatLngToGPX(pt, pttag, undefined, time);
      j++;
    }
    if (asroute) {
      gpx += "</rte>\n";
    } else {
      gpx += "  </trkseg>\n</trk>\n";
    }
  }
  gpx += "</gpx>\n";
  return gpx;
}

$("#track-download").click(function() {
  setEditMode(EDIT_NONE);
  setStatus("Formatting..", {spinner: true});
  var asroute = isChecked("#as-route");
  var nometadata = isChecked("#nometadata");
  var trackname =  getTrackName();
  if (trackname === NEW_TRACK_NAME) {
    askTrackName();
    trackname =  getTrackName();
  }
  var gpx = getGPX(trackname, /*savealt*/false, /*savetime*/false, asroute, nometadata);
  if (isSafari()) alert("A new page will open, press cmd+s (" + String.fromCharCode(8984) + "+s) to save file");
  var blob = new Blob([gpx],
    isSafari() ? {type: "text/plain;charset=utf-8"} :
      {type: "application/gpx+xml;charset=utf-8"}
  );
  saveAs(blob, trackname+".gpx");
  clearStatus();
});

function editableWaypoints(editable) {
  var wpts = waypoints.getLayers();
  for (var i=0; i< wpts.length; i++) {
    if (editable) {
      wpts[i].enableEdit();
    } else {
      wpts[i].disableEdit();
    }
  }
}

function mergeRouteToTrack() {
  if (!route) return;
  var initlen = track.getLatLngs().length;
  var pts = route._selectedRoute ? route._selectedRoute.coordinates : [];
  pts = L.PolyUtil.prune(pts, config.compressdefault);
  route.remove();
  route = undefined;
  routeStart = undefined;
  for (var j = 0; j < pts.length; j++) {
    track.addLatLng(pts[j]);
  }
  elevate(pts, function() {
    polystats.updateStatsFrom(initlen);
  });
}

function setRouteStart(latlng) {
  routeStart = latlng;
  $("#map").css("cursor", "alias");
}

function closeOverlays() {
  // close all
  $("#menu").hide();
  map.closePopup();
  hideElevation();
}

function restartRoute() {
  if (route) {
    route.remove();
    route = undefined;
    routeStart = undefined;
  }
  $("#map").css("cursor", "copy");
  var ll = track.getLatLngs();
  if (ll.length > 0) {
    setRouteStart(ll[ll.length-1]);
  }
}

function showGraphHopperCredit() {
  $("#map").append("<span class='gh-credit'>Powered by <a href='https://graphhopper.com/#directions-api'>GraphHopper API</a></span>");
}
function hideGraphHopperCredit() {
  $(".gh-credit").remove();
}
function showGraphHopperMessage(msg) {
  setStatus(msg, {timeout:5, class:"status-error"});
}

function setEditMode(mode) {
  closeOverlays();
  if (mode === editMode) {
    return;
  }
  switch (editMode) {
    case EDIT_MANUAL_TRACK:
      if (track) {
        track.disableEdit();
      }
      break;
    case EDIT_AUTO_TRACK:
      hideGraphHopperCredit();
      mergeRouteToTrack();
      break;
    case EDIT_MARKER:
      editableWaypoints(false);
      break;
    default:
  }
  map.editTools.stopDrawing();
  $("#edit-tools a").removeClass("control-selected");
  if (editMode > 0) { // exiting edit mode
    saveState();
    // reset mouse pointer
    $("#map").css("cursor", "");
  }
  switch (mode) {
    case EDIT_NONE:
      break;
    case EDIT_MANUAL_TRACK:
      $("#edit-manual").addClass("control-selected");
      track.enableEdit();
      track.editor.continueForward();
      break;
    case EDIT_AUTO_TRACK:
      $("#edit-auto").addClass("control-selected");
      showGraphHopperCredit();
      restartRoute();
      break;
    case EDIT_MARKER:
      $("#edit-marker").addClass("control-selected");
      $("#map").css("cursor", "url(img/marker-icon.png) 7 25,text");
      editableWaypoints(true);
      break;
    default:
      error("invalid edit mode: " + mode);
      return;
  }
  editMode = mode;
  $("#edit-tools").toggle(editMode > 0);
}

$("#compress").click(function() {
  // get & check input value
  var prunedist = $("#prunedist");
  var input = prunedist.val().trim();
  var tolerance;
  if (input) {
    tolerance = parseFloat(input);
  }
  if ((tolerance === undefined) || isNaN(tolerance)) {
    alert("Enter distance in meters");
    prunedist.focus();
    return;
  }

  if (track) {
    setEditMode(EDIT_NONE);
    var pts = track.getLatLngs();
    var pruned = L.PolyUtil.prune(pts, tolerance);
    var removedpts = (pts.length - pruned.length);
    if (removedpts> 0) {
      alert("Removed " + removedpts + " points out of " + pts.length + " (" + Math.round((removedpts / pts.length) * 100) + "%)");
      // switch to new values
      track.setLatLngs(pruned);
      saveState();
    } else {
      setStatus("Already optimized", {timeout:3});
    }
  }
});

function getMyIpLocation() {
  log("Getting location from IP address");
  var geoapi = "https://freegeoip.net/json/?callback=";
  $.getScript(geoapi+"setMyIpLocation")
  .fail(function( jqxhr, settings, exception ) {
    warn("freegeoip request failed");
  });
}

function setMyIpLocation(res) {
  setLocation({
    lat: res.latitude,
    lng: res.longitude
  }, false);
}

var myLocIcon = L.icon({
    iconUrl: 'img/mylocation.png',
    iconSize:     [48, 48],
    iconAnchor:   [24, 24]
});
var myLocMarker;
var myLocTimer;
var
  LOC_NONE = 0,
  LOC_ONCE = 1,
  LOC_CONTINUOUS = 2,
  showLocation = LOC_NONE;

function removeMyLocMarker() {
  if (showLocation == LOC_CONTINUOUS) {
    gotoMyLocation();
  } else {
    showLocation = LOC_NONE;
    if (myLocMarker) {
      myLocMarker.remove();
      myLocMarker = undefined;
    }
  }
}

function setLocation(pos, showIcon) {
  if (showLocation == LOC_NONE) return; // cancelled in the meantime
  var zoom = map.getZoom() ? map.getZoom() : config.display.zoom;
  map.setView(pos, zoom);
  if (myLocMarker) {
    clearTimeout(myLocTimer);
    myLocMarker.remove();
  }
  if (showIcon) {
    myLocMarker = new L.marker([ pos.lat, pos.lng ], {icon: myLocIcon, clickable:false});
    myLocMarker.addTo(map);
  }
  if (showIcon || (showLocation == LOC_CONTINUOUS)) {
    myLocTimer = setTimeout(removeMyLocMarker, 5000);
  } else {
    showLocation = LOC_NONE;
  }
}

function gotoMyLocation() {

  function gotLocation(position) {
    log("Got location");
    setLocation({
      lat: position.coords.latitude,
      lng: position.coords.longitude
    }, true);
  }

  function highAccuracyFailed(posError) {
    log("GPS location failed, trying low accuracy");
    navigator.geolocation.getCurrentPosition(
             gotLocation, lowAccuracyFailed,
             {maximumAge:60000, timeout:5000, enableHighAccuracy: false});
  }

  function lowAccuracyFailed(posError) {
    log("low accuracy geolococation failed");
    getMyIpLocation();
  }


  if (navigator.geolocation) {
    navigator.geolocation.getCurrentPosition(
      gotLocation, highAccuracyFailed,
       {maximumAge:0, timeout:5000, enableHighAccuracy: true});
  } else {
    log("no runtime geolococation available");
    getMyIpLocation();
  }
}

function getSavedPosition(_lat, _lng) {
  var vlat = getVal("wt.poslat", _lat);
  var vlng = getVal("wt.poslng", _lng);
  var pos = {
    lat: parseFloat(vlat),
    lng: parseFloat(vlng)
  };
  // ask for position on first use
  if (vlat == _lat && vlng == _lng) {
    setTimeout(function() {
      showLocation = LOC_ONCE;
      gotoMyLocation();
    }, 1000);
  }
  return pos;
}

function savePosition() {
  var pos = map.getCenter();
  saveValOpt("wt.poslat",pos.lat);
  saveValOpt("wt.poslng",pos.lng);
}

function saveEditMode() {
  if (editMode >= 0) saveValOpt("wt.editMode",editMode);
}

function saveTrack() {
  var trackname =  getTrackName();
  var numPts = track.getLatLngs().length + waypoints.getLayers().length;
  if (numPts < 1000) {
    var gpx = getGPX(trackname, /*savealt*/false, /*savetime*/false, /*asroute*/false, /*nometadata*/false);
    saveValOpt("wt.gpx",gpx);
  }
}

function saveState() {
  if (isStateSaved()) {
    saveTrack();
    savePosition();
    saveEditMode();
  }
}

function restoreEditMode() {
  var restoredMode = getVal("wt.editMode", EDIT_DEFAULT);
  setEditMode(parseInt(restoredMode));
}

function restorePosition() {
  var defpos = getSavedPosition(config.display.pos.lat, config.display.pos.lng);
  showLocation = LOC_ONCE;
  setLocation(defpos); // required to initialize map
}

function restoreTrack() {
  var gpx = getVal("wt.gpx", null);
  if (gpx) {
    loadCount = 0;
    fileloader.loadData(gpx, "dummy", "gpx");
    return true;
  } else {
    newTrack();
    return false;
  }
}

function saveInfo(save) {
  $("#save-info").hide();
  setChecked("#cfgsave", save);
  $("#cfgsave").change();
}
$("#save-yes").click(function (){
  saveInfo(true);
});
$("#save-no").click(function (){
  saveInfo(false);
});

function restoreState() {
  var isSaving = getVal("wt.saveState", null);
  if (isUnset(isSaving)) {
    $("#save-info").show();
  } else {
    setChecked("#cfgsave", isSaving == "true");
  }
  if (!restoreTrack()) {
    restorePosition();
  }
  restoreEditMode();
}

function clearSavedState() {
  storeVal("wt.gpx", undefined);
  storeVal("wt.poslat", undefined);
  storeVal("wt.poslng", undefined);
  storeVal("wt.editMode", undefined);
  storeVal("wt.baseLayer", undefined);
  storeVal("wt.overlays", undefined);
  storeVal("wt.activity", undefined);
  storeVal("wt.activities", undefined);
}

function saveMapType() {
  saveValOpt("wt.maptype", map.getMapTypeId());
}

function getProvider(name) {
  var p;
  if (name == "opentopomap") {
    p =  L.tileLayer('https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png', {
      maxZoom: 17,
      attribution: 'Map data: &copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>, <a href="https://viewfinderpanoramas.org">SRTM</a> | Map style: &copy; <a href="https://opentopomap.org">OpenTopoMap</a> (<a href="https://creativecommons.org/licenses/by-sa/3.0/">CC-BY-SA</a>)'
    });
  } else if (name == "osm:std") {
    p = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
      maxZoom: 19,
      attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>'
    });
  } else if (name == "osm:hot") {
    p = L.tileLayer('https://{s}.tile.openstreetmap.fr/hot/{z}/{x}/{y}.png', {
      maxZoom: 19,
      attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>'
    });
  } else if (name == "tf:cycle") {
    p = L.tileLayer('https://{s}.tile.thunderforest.com/cycle/{z}/{x}/{y}.png?apikey='+config.thunderforest.key(), {
      maxZoom: 18,
      attribution: '&copy; <a href="https://www.thunderforest.com/">Thunderforest</a> &copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>'
    });

  } else if (name == "sigma:cycle") {
    p = L.tileLayer('https://tiles1.sigma-dc-control.com/layer5/{z}/{x}/{y}.png', {
      maxZoom: 18,
      attribution: '&copy; <a href="http://www.sigmasport.com/" target="_blank">SIGMA Sport &reg;</a> Map data <a href="http://www.openstreetmap.org/" target="_blank">OpenStreetMap</a>'
    });
  } else if (name == "tf:outdoors") {
    p = L.tileLayer('https://{s}.tile.thunderforest.com/outdoors/{z}/{x}/{y}.png?apikey='+config.thunderforest.key(), {
      maxZoom: 18,
      attribution: '&copy; <a href="https://www.thunderforest.com/">Thunderforest</a>, &copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>'
    });
  } else if (name == "wmf:hikebike") {
    //p = L.tileLayer('http://{s}.tiles.wmflabs.org/hikebike/{z}/{x}/{y}.png', {
    p = L.tileLayer('https://tiles.wmflabs.org/hikebike/{z}/{x}/{y}.png', {
      maxZoom: 17,
      attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>'
    });
  }  else if (name == "esri:worldtopomap") {
    p = L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer/tile/{z}/{y}/{x}', {
      attribution: 'Tiles &copy; Esri &mdash; Esri and GIS Community'
    });
  }  else if (name == "esri:worldstreetmap") {
    p = L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer/tile/{z}/{y}/{x}', {
      attribution: 'Tiles &copy; Esri &mdash; Esri &amp; al.'
    });
  }  else if (name == "mtbmap") {
    p = L.tileLayer('http://tile.mtbmap.cz/mtbmap_tiles/{z}/{x}/{y}.png', {
      attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> &amp; USGS'
    });
  }  else if (name == "map1.eu") {
    p = L.tileLayer('http://beta.map1.eu/tiles/{z}/{x}/{y}.jpg', {
      maxZoom: 17,
      attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> &amp; <a href="http://map1.eu">map1.eu</a>'
    });
  } else if (name == 'google:roadmap') {
    p = L.tileLayer('https://{s}.google.com/vt/lyrs=m&x={x}&y={y}&z={z}',{
        maxZoom: 20,
        subdomains:['mt0','mt1','mt2','mt3'],
        attribution: '&copy; Google'
    });
  } else if (name == 'google:terrain') {
    p = L.tileLayer('https://{s}.google.com/vt/lyrs=p&x={x}&y={y}&z={z}',{
        maxZoom: 20,
        subdomains:['mt0','mt1','mt2','mt3'],
        attribution: '&copy; Google'
    });
  } else if (name == 'google:satellite') {
    p = L.tileLayer('https://{s}.google.com/vt/lyrs=s&x={x}&y={y}&z={z}',{
        maxZoom: 20,
        subdomains:['mt0','mt1','mt2','mt3'],
        attribution: '&copy; Google'
    });
  } else if (name == 'google:hybrid') {
    p = L.tileLayer('https://{s}.google.com/vt/lyrs=s,h&x={x}&y={y}&z={z}',{
        maxZoom: 20,
        subdomains:['mt0','mt1','mt2','mt3'],
        attribution: '&copy; Google'
    });
  } else if (name == 'wmf:hills') {
    //p = L.tileLayer('http://{s}.tiles.wmflabs.org/hillshading/{z}/{x}/{y}.png',{
    p = L.tileLayer('https://tiles.wmflabs.org/hillshading/{z}/{x}/{y}.png',{
      maxZoom: 17,
      attribution: 'Hillshading: SRTM3 v2 (<a href="https://www2.jpl.nasa.gov/srtm/">NASA</a>)'
    });
  } else if (name == 'lv:hike') {
    p = L.tileLayer('http://tile.waymarkedtrails.org/hiking/{z}/{x}/{y}.png',{
        maxZoom: 17,
      attribution: 'Hiking Routes: (<a href="http://hiking.lonvia.de">Lonvias Hiking Map</a>)'
    });
  } else if (name == 'lv:bike') {
    p = L.tileLayer('http://tile.waymarkedtrails.org/cycling/{z}/{x}/{y}.png',{
        maxZoom: 17,
      attribution: 'Cycling Routes: (<a href="http://cycling.lonvia.de">Lonvias Cycling Map</a>)'
    });
  } else if (name == 'fr:ignclassic') {
    p = L.tileLayer.wtms(
          "https://wxs.ign.fr/" + config.ign.key() + "/geoportail/wmts",
          {
            layer: 'GEOGRAPHICALGRIDSYSTEMS.MAPS',
            style: 'normal',
            tilematrixSet: "PM",
            format: 'image/jpeg',
            attribution: "&copy; <a href='http://www.ign.fr'>IGN</a>"
          });
  } else if (name == 'fr:ignexpress') {
    p = L.tileLayer.wtms(
          "https://wxs.ign.fr/" + config.ign.key() + "/geoportail/wmts",
          {
            layer: 'GEOGRAPHICALGRIDSYSTEMS.MAPS.SCAN-EXPRESS.STANDARD',
            style: 'normal',
            tilematrixSet: "PM",
            format: 'image/jpeg',
            attribution: "&copy; <a href='http://www.ign.fr'>IGN</a>"
          });
  } else if (name == 'fr:ignimagery') {
    p = L.tileLayer.wtms(
          "https://wxs.ign.fr/" + config.ign.key() + "/geoportail/wmts",
          {
            layer: 'ORTHOIMAGERY.ORTHOPHOTOS',
            style: 'normal',
            tilematrixSet: "PM",
            format: 'image/jpeg',
            attribution: "&copy; <a href='http://www.ign.fr'>IGN</a>"
          });
  } else if (name == 'spain:ignraster') {
    // https://github.com/sigdeletras/Leaflet.Spain.WMS
    p = L.tileLayer.wms('https://www.ign.es/wms-inspire/mapa-raster', {
        	layers: 'mtn_rasterizado',
        	format: 'image/png',
        	transparent: false,
        	continuousWorld : true,
        	attribution: '© <a href="http://www.ign.es/ign/main/index.do" target="_blank">Instituto Geográfico Nacional de España</a>'
        });
  } else if (name == 'spain:icgc') {
    p = L.tileLayer.wtms(
          "https://geoserveis.icgc.cat/icc_mapesmultibase/noutm/wmts/service",
          {
            layer: 'topo',
            style: 'normal',
            tilematrixSet: "GRID3857",
            format: 'image/jpeg',
            attribution: "&copy; Institut Cartogràfic i Geològic de Catalunya - ICGC"
          });
  }
  if (!p) {
    p = getProvider("osm:std");
  }
  return p;
}

var baseLayers = {
  "Open Topo": getProvider("opentopomap"),
  "OpenStreetMap": getProvider("osm:std"),
  "OpenCycleMap": getProvider("tf:cycle"),
  "Outdoors": getProvider("tf:outdoors"),
  "Sigma Cycle": getProvider("sigma:cycle"),
  "OSM HOT": getProvider("osm:hot"),
  "OSM HikeBike": getProvider("wmf:hikebike"),
  "ESRI Topo": getProvider("esri:worldtopomap"),
  "ESRI Street": getProvider("esri:worldstreetmap"),
  "MTB (*)": getProvider("mtbmap"),
  "Map1.eu (*)": getProvider("map1.eu"),
  "Google roads": getProvider('google:roadmap'),
  "Google Terrain": getProvider('google:terrain'),
  "Google Satellite": getProvider('google:satellite'),
  "Google Hybrid": getProvider('google:hybrid'),
  "FR IGN classic": getProvider("fr:ignclassic"),
  "FR IGN express": getProvider("fr:ignexpress"),
  //"FR IGN aerial": getProvider("fr:ignimagery")
  "ES IGN": getProvider("spain:ignraster"),
  "ES ICGC": getProvider("spain:icgc")
};
var overlays = {
  "Hillshading": getProvider("wmf:hills"),
  "Hiking Routes (*)": getProvider("lv:hike"),
  "Cycling Routes (*)": getProvider("lv:bike"),
};

L.control.layers(baseLayers, overlays).addTo(map);
map.addLayer(baseLayers[getVal("wt.baseLayer", config.display.map)] || baseLayers[config.display.map]);
map.on("baselayerchange", function(e) {
  saveValOpt("wt.baseLayer", e.name);
  $(".leaflet-control-layers").removeClass("leaflet-control-layers-expanded");
});
function getOverlays() {
  var v = getJsonVal("wt.overlays");
  return v || {};
}

function setOverlay(name, yesno) {
  var cfg = getOverlays();
  cfg[name] = yesno;
  saveJsonValOpt("wt.overlays", cfg);
}

{'fraction_non_alphanumeric': 0.20464511656122714, 'fraction_numerical': 0.006967674841840714, 'mean_word_length': 3.176027793862189, 'pattern_counts': {'":': 47, '<': 201, '<?xml version=': 2, '>': 205, 'https://': 71, 'lorem ipsum': 0, 'www.': 44, 'xml': 14}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1491166', 'n_tokens_mistral': 21084, 'n_tokens_neox': 20572, 'n_words': 3596}