text: 
  //404 Motivation Not Found, 地球上の2地点間の距離を取得するアルゴリズム(ヒュベニ or 球面三角法)比較, https://tech-blog.s-yoshiki.com/2018/05/92/
  function hubeny(lat1, lng1, lat2, lng2) {
      function rad(deg) {
          return deg * Math.PI / 180;
      }
      //degree to radian
      lat1 = rad(lat1);
      lng1 = rad(lng1);
      lat2 = rad(lat2);
      lng2 = rad(lng2);
  
      // 緯度差
      var latDiff = lat1 - lat2;
      // 経度差算
      var lngDiff = lng1 - lng2;
      // 平均緯度
      var latAvg = (lat1 + lat2) / 2.0;
      // 赤道半径
      var a = 6378137.0;
      // 極半径
      var b = 6356752.314140356;
      // 第一離心率^2
      var e2 = 0.00669438002301188;
      // 赤道上の子午線曲率半径
      var a1e2 = 6335439.32708317;
  
      var sinLat = Math.sin(latAvg);
      var W2 = 1.0 - e2 * (sinLat * sinLat);
  
      // 子午線曲率半径M
      var M = a1e2 / (Math.sqrt(W2) * W2);
      // 卯酉線曲率半径
      var N = a / Math.sqrt(W2);
  
      const t1 = M * latDiff;
      const t2 = N * Math.cos(latAvg) * lngDiff;
      return Math.sqrt((t1 * t1) + (t2 * t2));
  }
  function sphericalTrigonometry(lat1, lng1, lat2, lng2) {
      // 赤道半径
      var R = 6378137.0;
  
      function rad(deg) {
          return deg * Math.PI / 180;
      }
  
      return R *
          Math.acos(
              Math.cos(rad(lat1)) *
              Math.cos(rad(lat2)) *
              Math.cos(rad(lng2) - rad(lng1)) +
              Math.sin(rad(lat1)) *
              Math.sin(rad(lat2))
          );
  }
  
  //Andrei Sambra, Parsing a Link header in Javascript, https://coderwall.com/p/zrlulq/parsing-a-link-header-in-javascript
  // Unquote string (utility)
  function unquote(value) {
      if (value.charAt(0) == '"' && value.charAt(value.length - 1) == '"') return value.substring(1, value.length - 1);
      return value;
  }
  
  // Parse a Link header
  function parseLinkHeader(header) {
      var linkexp = /<[^>]*>\s*(\s*;\s*[^\(\)<>@,;:"\/\[\]\?={} \t]+=(([^\(\)<>@,;:"\/\[\]\?={} \t]+)|("[^"]*")))*(,|$)/g;
      var paramexp = /[^\(\)<>@,;:"\/\[\]\?={} \t]+=(([^\(\)<>@,;:"\/\[\]\?={} \t]+)|("[^"]*"))/g;
  
      var matches = header.match(linkexp);
      var rels = new Object();
      for (i = 0; i < matches.length; i++) {
          var split = matches[i].split('>');
          var href = split[0].substring(1);
          var ps = split[1];
          var link = new Object();
          link.href = href;
          var s = ps.match(paramexp);
          for (j = 0; j < s.length; j++) {
              var p = s[j];
              var paramsplit = p.split('=');
              var name = paramsplit[0];
              link[name] = unquote(paramsplit[1]);
          }
  
          if (link.rel != undefined) {
              rels[link.rel] = link;
          }
      }
  
      return rels;
  }
  //-------------
  
  //graphhopper, https://github.com/graphhopper/graphhopper/blob/d70b63660ac5200b03c38ba3406b8f93976628a6/web/src/main/webapp/js/ghrequest.js#L139
  function decodePath(encoded, is3D) {
      var len = encoded.length;
      var index = 0;
      var array = [];
      var lat = 0;
      var lng = 0;
      var ele = 0;
  
      while (index < len) {
          var b;
          var shift = 0;
          var result = 0;
          do {
              b = encoded.charCodeAt(index++) - 63;
              result |= (b & 0x1f) << shift;
              shift += 5;
          } while (b >= 0x20);
          var deltaLat = ((result & 1) ? ~(result >> 1) : (result >> 1));
          lat += deltaLat;
  
          shift = 0;
          result = 0;
          do {
              b = encoded.charCodeAt(index++) - 63;
              result |= (b & 0x1f) << shift;
              shift += 5;
          } while (b >= 0x20);
          var deltaLon = ((result & 1) ? ~(result >> 1) : (result >> 1));
          lng += deltaLon;
  
          if (is3D) {
              // elevation
              shift = 0;
              result = 0;
              do {
                  b = encoded.charCodeAt(index++) - 63;
                  result |= (b & 0x1f) << shift;
                  shift += 5;
              } while (b >= 0x20);
              var deltaEle = ((result & 1) ? ~(result >> 1) : (result >> 1));
              ele += deltaEle;
              array.push([lng * 1e-5, lat * 1e-5, ele / 100]);
          } else
              array.push([lng * 1e-5, lat * 1e-5]);
      }
      // var end = new Date().getTime();
      // console.log("decoded " + len + " coordinates in " + ((end - start) / 1000) + "s");
      return array;
  };
  
  function getLatLonZoom(url) {
    const match = url.match(/www\.mapillary\.com\/app\/\?lat=(-?\d[0-9.]*)&lng=(-?\d[0-9.]*)&z=(\d{1,2})/);
    if (match) {
  	const [, lat, lon,zoom] = match;
  	return [lat, lon, zoom];
    }
  }
  function latLonZoomToBbox(lat, lon, zoom) {
  	const part = Math.pow(0.5,zoom);
  	const minlon = Number(lon) - 360*part/2;
  	const maxlon = Number(lon) + 360*part/2;
  	const minlat = Number(lat) - 180*part/2;
  	const maxlat = Number(lat) + 180*part/2;
  	return [minlon, minlat, maxlon, maxlat];
  
  }
  