<commit_before>var mission_id = 0;
var position_id = 0;
var data_url = "http://spacenear.us/tracker/datanew.php";
var receivers_url = "http://spacenear.us/tracker/receivers.php";
var predictions_url = "http://spacenear.us/tracker/get_predictions.php?vehicles=";

var habitat_max = 400;
var habitat_url = "http://habitat.habhub.org/habitat/";
var habitat_url_payload_telemetry = habitat_url + "_design/payload_telemetry/_view/payload_time?startkey=[%22{ID}%22,{START}]&endkey=[%22{ID}%22,{END}]&include_docs=true&limit=" + habitat_max + "&skip=";

var host_url = "";
var markers_url = "img/markers/";
var vehicles = {};
var elm_uuid = 0;

var receiver_names = [];
var receivers = [];

var got_positions = false;
var zoomed_in = false;
var max_positions = 0; // maximum number of positions that ajax request should return (0 means no maximum)
var follow_vehicle = null;
var graph_vehicle = null;
var manual_pan = false;

var car_index = 0;
var car_colors = ["blue", "red", "green", "yellow"];
var balloon_index = 0;
var balloon_colors_name = ["red", "blue", "green", "yellow", "purple", "orange", "cyan"];
var balloon_colors = ["#f00", "blue", "green", "#FDFC30", "#c700e6", "#ff8a0f", "#0fffca"];

var map = null;
var overlay = null;
var layer_clouds = null;

var notamOverlay = null;

var modeList = [
//    "Position",
    "1 hour",
    "3 hours",
    "6 hours",
    "1 day",
    "2 days",
    "3 days",
    "All",
];
var modeDefault = "1 day";
var modeDefaultMobile = "1 hour";

// order of map elements
var Z_RANGE = 1;
var Z_STATION = 2;
var Z_PATH = 10;
var Z_ME = 11;
var Z_SHADOW = 1000000;
var Z_CAR = 1000001;
var Z_PAYLOAD = 1000002;

// localStorage vars
var ls_receivers = false;
var ls_pred = false;

var plot = null;
var plot_open = false;
var plot_holder = "#telemetry_graph .holder";
var plot_options = {
    crosshair: {
        mode: "x"
    },
    legend: {
        show: true,
        sorted: false,
        position: 'nw',
        noColumns: 1,
        backgroundColor: null,
        backgroundOpacity: 0
    },
    grid: {
        show: true,
        hoverable: true,
        aboveData: true,
        borderWidth: 0,
    },
    selection: {
        mode: "x"
    },
    yaxes: [
        {show: false, min: 0, max: 0},
        {show: false, min: 0, max: 0},
        {show: false, min: 0 },
        {show: false, min: 0 },
        {show: false, min: 0 },
        {show: false, min: 0 },
        {show: false, min: 0 },
        {show: false, min: 0 },
        {show: false, min: 0 },
    ],
    xaxes: [
        {
            show: true,
            mode: "time",
            timeformat: "%m/%d %H:%M"
        }
    ]
};

// aprs overlay
var overlayAPRS = new google.maps.ImageMapType({
    getTileUrl: function(coord, zoom) {
        var n = Math.pow(2,zoom);
        return (coord.y<0 || coord.y>=n || zoom > 6) ? null : "http://" +
                                                              ['a','b','c'][Math.abs(coord.x+coord.y)%3] +
                                                              ".tiles.tracker.habhub.org/aprs/tile_" +
                                                              zoom + "_" +
                                                              wrapTiles(coord.x,zoom) + "_" +
                                                              coord.y + ".png";
    },
    tileSize: new google.maps.Size(256,256)
});

// weather
var weatherOverlayId = "nexrad-n0q-900913";
var weatherOverlay = new google.maps.ImageMapType({
    getTileUrl: function(tile, zoom) {
        return "http://mesonet.agron.iastate.edu/cache/tile.py/1.0.0/"+weatherOverlayId+"/" + zoom + "/" + tile.x + "/" + tile.y +".png";
    },
    tileSize: new google.maps.Size(256, 256),
    opacity:0.8,
    isPng: true
});

var weatherImageOverlayList = {
    'nrl-global-cloudtop': ['http://www.nrlmry.navy.mil/archdat/global/stitched/cloudtop/LATEST.jpg', [[-76, -179.9999], [76, 179.75]]],
    'nrl-global-ir': ['http://www.nrlmry.navy.mil/archdat/global/stitched/ir/LATEST.jpg', [[-65, -179.9999], [65, 179.75]]],
    'nrl-global-vapor': ['http://www.nrlmry.navy.mil/archdat/global/stitched/vapor/LATEST.jpg', [[-65, -179.9999], [65, 179.75]]],
    'meteosat-Odeg-MPE': ['http://oiswww.eumetsat.int/IPPS/html/GE/MET0D/GE_MET0D_VP-MPE.png', [[-57.492200, -57.492200], [57.492200, 57.492200]]],
    'meteosat-iodc-MPE': ['http://oiswww.eumetsat.int/IPPS/html/GE/IODC/GE_IODC_VP-MPE.png', [[-59.7901, -2.7919], [59.7901, 116.7913]]]
};

var weatherImageOverlay = new google.maps.GroundOverlay();
var weatherGoogleRadar = new google.maps.KmlLayer({url:'http://mw1.google.com/mw-weather/radar/root.kmz', preserveViewport: true});

var offline = {
    get: function(key) {
        if(typeof localStorage == 'undefined') return null;

        return JSON.parse(localStorage.getItem(key));
    },
    set: function(key, object) {
        if(typeof localStorage == 'undefined') return null;

        return localStorage.setItem(key, JSON.stringify(object));
    },
};

var DEG_TO_RAD = Math.PI / 180.0;
var EARTH_RADIUS = 6371000.0;

// calculates look angles between two points
// format of a and b should be {lon: 0, lat: 0, alt: 0}
// returns {elevention: 0, azimut: 0, bearing: "", range: 0}
//
// based on earthmath.py
// Copyright 2012 (C) <NAME>; GNU GPL 3
function calculate_lookangles(a, b) {
    // degrees to radii
    a.lat = a.lat * DEG_TO_RAD;
    a.lon = a.lon * DEG_TO_RAD;
    b.lat = b.lat * DEG_TO_RAD;
    b.lon = b.lon * DEG_TO_RAD;

    var d_lon = b.lon - a.lon;
    var sa = Math.cos(b.lat) * Math.sin(d_lon);
    var sb = (Math.cos(a.lat) * Math.sin(b.lat)) - (Math.sin(a.lat) * Math.cos(b.lat) * Math.cos(d_lon));
    var bearing = Math.atan2(sa, sb);
    var aa = Math.sqrt(Math.pow(sa, 2) + Math.pow(sb, 2));
    var ab = (Math.sin(a.lat) * Math.sin(b.lat)) + (Math.cos(a.lat) * Math.cos(b.lat) * Math.cos(d_lon));
    var angle_at_centre = Math.atan2(aa, ab);
    var great_circle_distance = angle_at_centre * EARTH_RADIUS;

    ta = EARTH_RADIUS + a.alt;
    tb = EARTH_RADIUS + b.alt;
    ea = (Math.cos(angle_at_centre) * tb) - ta;
    eb = Math.sin(angle_at_centre) * tb;
    var elevation = Math.atan2(ea, eb) / DEG_TO_RAD;

    // Use Math.coMath.sine rule to find unknown side.
    var distance = Math.sqrt(Math.pow(ta, 2) + Math.pow(tb, 2) - 2 * tb * ta * Math.cos(angle_at_centre));

    // Give a bearing in range 0 <= b < 2pi
    bearing += (bearing < 0) ? 2 * Math.PI : 0;
    bearing /= DEG_TO_RAD;

    var value = Math.round(bearing % 90);
    value = ((bearing > 90 && bearing < 180) || (bearing > 270 && bearing < 360)) ? 90 - value : value;

    var str_bearing = "" + ((bearing < 90 || bearing > 270) ? 'N' : 'S')+ " " + value + '° ' + ((bearing < 180) ? 'E' : 'W');

    return {
        'elevation': elevation,
        'azimuth': bearing,
        'range': distance,
        'bearing': str_bearing
    };
}

function update_lookangles(vcallsign) {
    if(GPS_ts === null) { return; }
    else if($("#lookanglesbox span").first().is(":hidden")) {
        $("#lookanglesbox div").hide().parent().find("span").show();
    }

    var a = {lat: GPS_lat, lon: GPS_lon, alt: GPS_alt};

    var xref = vehicles[vcallsign].curr_position;
    var b = {lat: parseFloat(xref.gps_lat), lon: parseFloat(xref.gps_lon), alt: parseFloat(xref.gps_alt)};

    var look = calculate_lookangles(a,b);

    $("#lookanglesbox .azimuth").text("Azimuth: " + roundNumber(look.azimuth, 2) + "°");
    $("#lookanglesbox .bearing").text(look.bearing);
    $("#lookanglesbox .elevation").text("Elevation: " + roundNumber(look.elevation, 2) + "°");

    var range_string = "";
    if(offline.get('opt_imperial')) {
        range_string =  Math.round(look.range * 0.000621371192) + " miles";
    } else {
        range_string = (look.range < 10000) ? Math.round(look.range) + "m" : (Math.round(look.range/100)/10) + " km";
    }
    $("#lookanglesbox .range").text(range_string);
}

function makeQuad(x, y, zoom) {
    var quad = "";
    for (var i = zoom; i > 0; i--) {
      var mask = 1 << (i - 1);
      var cell = 0;
      if ((x & mask) !== 0) cell++;
      if ((y & mask) !== 0) cell += 2;
      quad += cell;
    }
    return quad;
}

// wraps tiles horizontally, returns x
function wrapTiles(x, zoom) {
    var n = Math.pow(2,zoom);
    return (x<0) ? (n+(x%n))%n : x%n;
}

// map type list
// format: [ name, attr, minZoom, maxZoom, getTileUrl function ]
var maptypes = {
    bing_os: [
        'Ordnance Survey (UK)',
        'Bing.com & Ordnance Survey',
         10,
         17,
         function(xy,z) { return 'http://ecn.t'+((Math.abs(xy.x+xy.y)%3)+1)+'.tiles.virtualearth.net/tiles/r'+makeQuad(xy.x, xy.y, z)+'?g=2689&lbl=l1&productSet=mmOS'; }
    ],
    osm: [
        'OSM',
        'OpenStreetMaps.org',
         1,
         19,
         function(xy,z) { var n = Math.pow(2,z); return (xy.y<0 || xy.y>=n) ? null : 'http://'+['a','b','c'][Math.abs(xy.x+xy.y)%3]+'.tile.openstreetmap.org/'+z+'/'+wrapTiles(xy.x,z)+'/'+xy.y+'.png'; }
    ],
    dark_matter: [
        'Dark Matter',
        '&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors, &copy; <a href="http://cartodb.com/attributions">CartoDB</a>',
         1,
         19,
         function(xy,z) { var n = Math.pow(2,z); return (xy.y<0 || xy.y>=n) ? null : 'http://'+['a','b','c'][Math.abs(xy.x+xy.y)%3]+'.basemaps.cartocdn.com/dark_all/'+z+'/'+wrapTiles(xy.x,z)+'/'+xy.y+'.png'; }
    ],
    osm_toner: [
        'OSM Toner',
        'Stamen.org Toner',
         1,
         18,
         function(xy,z) { var n = Math.pow(2,z); return (xy.y<0 || xy.y>=n) ? null : 'http://'+['a','b','c','d'][Math.abs(xy.x+xy.y)%4]+'.tile.stamen.com/toner/'+z+'/'+wrapTiles(xy.x,z)+'/'+xy.y+'.png'; }
    ],
    osm_watercolor: [
        'OSM Watercolor',
        'Stamen.org Watercolor',
         1,
         18,
         function(xy,z) { var n = Math.pow(2,z); return (xy.y<0 || xy.y>=n) ? null : 'http://'+['a','b','c','d'][Math.abs(xy.x+xy.y)%4]+'.tile.stamen.com/watercolor/'+z+'/'+wrapTiles(xy.x,z)+'/'+xy.y+'.png'; }
    ]
};

// generate a list of names for the UI
var maptype_ids = ["roadmap","satellite","terrain"];
for(var i in maptypes) maptype_ids.push(i);


// mousemove event throttle hack for smoother maps pan on firefox and IE
// taken from: http://stackoverflow.com/questions/22306130/how-to-limit-google-maps-api-lag-when-panning-the-map-with-lots-of-markers-and-p

var mthrottle_last = {
                        time : 0,     // last time we let an event pass.
                        x    : -100,  // last x position af the event that passed.
                        y    : -100   // last y position af the event that passed.
                     };
var mthrottle_period = 16;   // ms - don't let pass more than one event every 100ms.
var mthrottle_space  = 40;    // px - let event pass if distance between the last and
                              //      current position is greater than 2 px.

function throttle_events(event) {
    var now = window.performance.now();
    var distance = Math.sqrt(Math.pow(event.clientX - mthrottle_last.x, 2) + Math.pow(event.clientY - mthrottle_last.y, 2));
    var time = now - mthrottle_last.time;
    if (distance * time < mthrottle_space * mthrottle_period) {    //event arrived too soon or mouse moved too little or both
        if (event.stopPropagation) { // W3C/addEventListener()
            event.stopPropagation();
        } else { // Older IE.
            event.cancelBubble = true;
        }
    } else {
        mthrottle_last.time = now;
        mthrottle_last.x    = event.clientX;
        mthrottle_last.y    = event.clientY;
    }
}


function clean_refresh(text, force, history_step) {
    force = !!force;
    history_step = !!history_step;

    if(text == wvar.mode && !force) return false;
    if(ajax_inprogress) return false;

    stopAjax();

    // reset mode if, invalid mode is specified
    if(modeList.indexOf(text) == -1) text = (is_mobile) ? modeDefaultMobile : modeDefault;

    wvar.mode = text;
    tmpC.select(text);

    position_id = 0;

    mapInfoBox.close();

    // clear vehicles
    var callsign;
    for(callsign in vehicles) {
        removePrediction(callsign);
        vehicles[callsign].kill();
    }

    // clear hysplit
    for(callsign in hysplit) {
        hysplit[callsign].setMap(null);
    }

    car_index = 0;
    balloon_index = 0;
    stopFollow(force);

    // add loading spinner in the vehicle list
    $('#main .empty').parent().remove();
    $("#main .portrait,#main .landscape").append(
        '<div class="row vehicle'+elm_uuid+'"><div class="header empty">' +
        '<img style="width:90px;height:30px" src="img/hab-spinner.gif"/></div></div>'
    );
    listScroll.refresh();

    lhash_update(history_step);
    refresh();
    refreshPredictions();

    return true;
}

var tmpC;

function load() {
    //initialize map object
    map = new google.maps.Map(document.getElementById('map'), {
        zoom: 5,
        center: new google.maps.LatLng(53.467511,-2.2338940),
        mapTypeId: google.maps.MapTypeId.ROADMAP,
        mapTypeControlOptions: {
            mapTypeIds: maptype_ids,
            style: google.maps.MapTypeControlStyle.DROPDOWN_MENU
        },
        keyboardShortcuts: false,
        streetViewControl: false,
        rotateControl: false,
        panControl: false,
        scaleControl: true,
        zoomControl: true,
        zoomControlOptions: {
            style: google.maps.ZoomControlStyle.DEFAULT
        },
        scrollwheel: true
    });

    if(window.performance && window.performance.now && window.navigator.userAgent.indexOf("Firefox") != -1) {
        document.getElementById('map').addEventListener("mousemove", throttle_events, true);
    }

    // register custom map types
    for(var i in maptypes) {
        map.mapTypes.set(i, new google.maps.ImageMapType({
            name: maptypes[i][0],
            minZoom: maptypes[i][2],
            maxZoom: maptypes[i][3],
            getTileUrl: maptypes[i][4],
            tileSize: new google.maps.Size(256, 256),
        }));
    }

    var customTileAttr = new google.maps.StatusTextControl({
        text: '',
        map: map,
        position: google.maps.ControlPosition.BOTTOM_RIGHT,
    });

    google.maps.event.addListener(map, 'maptypeid_changed', function() {
        var id = this.getMapTypeId();
        customTileAttr.setText((id in maptypes) ? maptypes[id][1] : '');
    });

    // update current position if we geolocation is available
    if(currentPosition) updateCurrentPosition(currentPosition.lat, currentPosition.lon);

    // initalize nite overlay
    nite.init(map);
    if(!offline.get('opt_daylight')) nite.hide();
    setInterval(function() { nite.refresh(); }, 60000); // 1min

    // we need a dummy overlay to access getProjection()
    overlay = new google.maps.OverlayView();
    overlay.draw = function() {};
    overlay.setMap(map);

    // status message boxes
    var statusElm = new google.maps.StatusTextControl({
        map: map,
        position: google.maps.ControlPosition.RIGHT_BOTTOM,
        text: "<span id='stText'></span><span> Updated: </span><i class='friendly-dtime' id='stTimer'>never</i>"
    });


    google.maps.event.addListener(map, 'zoom_changed', function() {
        updateZoom();
    });

    google.maps.event.addListener(map, 'dragstart', function() {
        if(!wvar.embeded) manual_pan = true;
    });

    // only start population the map, once its completely loaded
    google.maps.event.addListenerOnce(map, 'idle', function(){
        load_hash(null);

        // initialize period menu
        tmpC = new google.maps.DropDownControl({
            map: map,
            title: "Show activity for given period",
            //position: google.maps.ControlPosition.TOP_RIGHT,
            position: google.maps.ControlPosition.RIGHT_TOP,
            headerPrefix: "Last: ",
            list: modeList,
            listDefault: modeList.indexOf(wvar.mode),
            callback: clean_refresh,
        });

        google.maps.event.addListener(map, 'idle', function() {
            lhash_update();
        });
        google.maps.event.addListener(map, 'maptypeid_changed', function() {
            lhash_update();
        });

        startAjax();
    });

    // animate-in the timebox,
    setTimeout(function() {
        var elm = $("#timebox");

        if(is_mobile) $(".slickbox").css({left:'5px'});
        var origW = elm.width();
        var iconW = elm.find("svg").width();

        if(offline.get('opt_hide_timebox')) {
            elm.removeClass('animate').hide();
            $("#lookanglesbox").css({top:'7px'});
        }

        // prep for animation
        $(".slickbox.animate").css({width:iconW}).find("span").hide();

        if(!offline.get('opt_hide_timebox')) {
            // animate timebox
            elm.fadeIn(500,"easeOut").animate({width:origW},400,"easeOut", function() {
              $("#timebox span").fadeIn(500, "easeOut");
            });
        }

        // animate lookanglesbox, delayed start by 300ms
        $("#lookanglesbox").delay(200).fadeIn(500,"easeOut").animate({width:origW},400,"easeOut", function() {
          if(GPS_ts === null) {
              $("#lookanglesbox .nopos").fadeIn(500, "easeOut");
          } else if($("#lookanglesbox span:first").is(":hidden")) {
              $("#lookanglesbox .nofollow").fadeIn(500, "easeOut");
          }
        });

        // if we there is enough screen space open aboutbox on startup
        if(!is_mobile && !offline.get('opt_nowelcome') && $(window).width() > 900) $('.nav li.about').click();

    }, 500);

    // initialize clouds layer
    layers_clouds = new google.maps.weather.CloudLayer();
    if(offline.get('opt_layers_clouds')) layers_clouds.setMap(map);

    // load if aprs layer, if selected
    if(offline.get('opt_layers_aprs')) map.overlayMapTypes.setAt("1", overlayAPRS);
}

function panTo(vcallsign) {
    if(!vcallsign) return;

    // update lookangles
    update_lookangles(vcallsign);

    // pan map
    //if(vehicles[vcallsign].marker_shadow) map.panTo(vehicles[vcallsign].marker_shadow.getPosition());
    //else map.panTo(vehicles[vcallsign].marker.getPosition());
    map.panTo(vehicles[vcallsign].marker.getPosition());
}

function title_case(s) {
  return s.replace(/\w\S*/g, function(txt) {
    return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
  });
}

function guess_name(key) {
  return title_case(key.replace(/_/g, " "));<commit_msg>fix error trying to pan notexistant vehicle<commit_after>var mission_id = 0;
var position_id = 0;
var data_url = "http://spacenear.us/tracker/datanew.php";
var receivers_url = "http://spacenear.us/tracker/receivers.php";
var predictions_url = "http://spacenear.us/tracker/get_predictions.php?vehicles=";

var habitat_max = 400;
var habitat_url = "http://habitat.habhub.org/habitat/";
var habitat_url_payload_telemetry = habitat_url + "_design/payload_telemetry/_view/payload_time?startkey=[%22{ID}%22,{START}]&endkey=[%22{ID}%22,{END}]&include_docs=true&limit=" + habitat_max + "&skip=";

var host_url = "";
var markers_url = "img/markers/";
var vehicles = {};
var elm_uuid = 0;

var receiver_names = [];
var receivers = [];

var got_positions = false;
var zoomed_in = false;
var max_positions = 0; // maximum number of positions that ajax request should return (0 means no maximum)
var follow_vehicle = null;
var graph_vehicle = null;
var manual_pan = false;

var car_index = 0;
var car_colors = ["blue", "red", "green", "yellow"];
var balloon_index = 0;
var balloon_colors_name = ["red", "blue", "green", "yellow", "purple", "orange", "cyan"];
var balloon_colors = ["#f00", "blue", "green", "#FDFC30", "#c700e6", "#ff8a0f", "#0fffca"];

var map = null;
var overlay = null;
var layer_clouds = null;

var notamOverlay = null;

var modeList = [
//    "Position",
    "1 hour",
    "3 hours",
    "6 hours",
    "1 day",
    "2 days",
    "3 days",
    "All",
];
var modeDefault = "1 day";
var modeDefaultMobile = "1 hour";

// order of map elements
var Z_RANGE = 1;
var Z_STATION = 2;
var Z_PATH = 10;
var Z_ME = 11;
var Z_SHADOW = 1000000;
var Z_CAR = 1000001;
var Z_PAYLOAD = 1000002;

// localStorage vars
var ls_receivers = false;
var ls_pred = false;

var plot = null;
var plot_open = false;
var plot_holder = "#telemetry_graph .holder";
var plot_options = {
    crosshair: {
        mode: "x"
    },
    legend: {
        show: true,
        sorted: false,
        position: 'nw',
        noColumns: 1,
        backgroundColor: null,
        backgroundOpacity: 0
    },
    grid: {
        show: true,
        hoverable: true,
        aboveData: true,
        borderWidth: 0,
    },
    selection: {
        mode: "x"
    },
    yaxes: [
        {show: false, min: 0, max: 0},
        {show: false, min: 0, max: 0},
        {show: false, min: 0 },
        {show: false, min: 0 },
        {show: false, min: 0 },
        {show: false, min: 0 },
        {show: false, min: 0 },
        {show: false, min: 0 },
        {show: false, min: 0 },
    ],
    xaxes: [
        {
            show: true,
            mode: "time",
            timeformat: "%m/%d %H:%M"
        }
    ]
};

// aprs overlay
var overlayAPRS = new google.maps.ImageMapType({
    getTileUrl: function(coord, zoom) {
        var n = Math.pow(2,zoom);
        return (coord.y<0 || coord.y>=n || zoom > 6) ? null : "http://" +
                                                              ['a','b','c'][Math.abs(coord.x+coord.y)%3] +
                                                              ".tiles.tracker.habhub.org/aprs/tile_" +
                                                              zoom + "_" +
                                                              wrapTiles(coord.x,zoom) + "_" +
                                                              coord.y + ".png";
    },
    tileSize: new google.maps.Size(256,256)
});

// weather
var weatherOverlayId = "nexrad-n0q-900913";
var weatherOverlay = new google.maps.ImageMapType({
    getTileUrl: function(tile, zoom) {
        return "http://mesonet.agron.iastate.edu/cache/tile.py/1.0.0/"+weatherOverlayId+"/" + zoom + "/" + tile.x + "/" + tile.y +".png";
    },
    tileSize: new google.maps.Size(256, 256),
    opacity:0.8,
    isPng: true
});

var weatherImageOverlayList = {
    'nrl-global-cloudtop': ['http://www.nrlmry.navy.mil/archdat/global/stitched/cloudtop/LATEST.jpg', [[-76, -179.9999], [76, 179.75]]],
    'nrl-global-ir': ['http://www.nrlmry.navy.mil/archdat/global/stitched/ir/LATEST.jpg', [[-65, -179.9999], [65, 179.75]]],
    'nrl-global-vapor': ['http://www.nrlmry.navy.mil/archdat/global/stitched/vapor/LATEST.jpg', [[-65, -179.9999], [65, 179.75]]],
    'meteosat-Odeg-MPE': ['http://oiswww.eumetsat.int/IPPS/html/GE/MET0D/GE_MET0D_VP-MPE.png', [[-57.492200, -57.492200], [57.492200, 57.492200]]],
    'meteosat-iodc-MPE': ['http://oiswww.eumetsat.int/IPPS/html/GE/IODC/GE_IODC_VP-MPE.png', [[-59.7901, -2.7919], [59.7901, 116.7913]]]
};

var weatherImageOverlay = new google.maps.GroundOverlay();
var weatherGoogleRadar = new google.maps.KmlLayer({url:'http://mw1.google.com/mw-weather/radar/root.kmz', preserveViewport: true});

var offline = {
    get: function(key) {
        if(typeof localStorage == 'undefined') return null;

        return JSON.parse(localStorage.getItem(key));
    },
    set: function(key, object) {
        if(typeof localStorage == 'undefined') return null;

        return localStorage.setItem(key, JSON.stringify(object));
    },
};

var DEG_TO_RAD = Math.PI / 180.0;
var EARTH_RADIUS = 6371000.0;

// calculates look angles between two points
// format of a and b should be {lon: 0, lat: 0, alt: 0}
// returns {elevention: 0, azimut: 0, bearing: "", range: 0}
//
// based on earthmath.py
// Copyright 2012 (C) <NAME>; GNU GPL 3
function calculate_lookangles(a, b) {
    // degrees to radii
    a.lat = a.lat * DEG_TO_RAD;
    a.lon = a.lon * DEG_TO_RAD;
    b.lat = b.lat * DEG_TO_RAD;
    b.lon = b.lon * DEG_TO_RAD;

    var d_lon = b.lon - a.lon;
    var sa = Math.cos(b.lat) * Math.sin(d_lon);
    var sb = (Math.cos(a.lat) * Math.sin(b.lat)) - (Math.sin(a.lat) * Math.cos(b.lat) * Math.cos(d_lon));
    var bearing = Math.atan2(sa, sb);
    var aa = Math.sqrt(Math.pow(sa, 2) + Math.pow(sb, 2));
    var ab = (Math.sin(a.lat) * Math.sin(b.lat)) + (Math.cos(a.lat) * Math.cos(b.lat) * Math.cos(d_lon));
    var angle_at_centre = Math.atan2(aa, ab);
    var great_circle_distance = angle_at_centre * EARTH_RADIUS;

    ta = EARTH_RADIUS + a.alt;
    tb = EARTH_RADIUS + b.alt;
    ea = (Math.cos(angle_at_centre) * tb) - ta;
    eb = Math.sin(angle_at_centre) * tb;
    var elevation = Math.atan2(ea, eb) / DEG_TO_RAD;

    // Use Math.coMath.sine rule to find unknown side.
    var distance = Math.sqrt(Math.pow(ta, 2) + Math.pow(tb, 2) - 2 * tb * ta * Math.cos(angle_at_centre));

    // Give a bearing in range 0 <= b < 2pi
    bearing += (bearing < 0) ? 2 * Math.PI : 0;
    bearing /= DEG_TO_RAD;

    var value = Math.round(bearing % 90);
    value = ((bearing > 90 && bearing < 180) || (bearing > 270 && bearing < 360)) ? 90 - value : value;

    var str_bearing = "" + ((bearing < 90 || bearing > 270) ? 'N' : 'S')+ " " + value + '° ' + ((bearing < 180) ? 'E' : 'W');

    return {
        'elevation': elevation,
        'azimuth': bearing,
        'range': distance,
        'bearing': str_bearing
    };
}

function update_lookangles(vcallsign) {
    if(GPS_ts === null) { return; }
    else if($("#lookanglesbox span").first().is(":hidden")) {
        $("#lookanglesbox div").hide().parent().find("span").show();
    }

    var a = {lat: GPS_lat, lon: GPS_lon, alt: GPS_alt};

    var xref = vehicles[vcallsign].curr_position;
    var b = {lat: parseFloat(xref.gps_lat), lon: parseFloat(xref.gps_lon), alt: parseFloat(xref.gps_alt)};

    var look = calculate_lookangles(a,b);

    $("#lookanglesbox .azimuth").text("Azimuth: " + roundNumber(look.azimuth, 2) + "°");
    $("#lookanglesbox .bearing").text(look.bearing);
    $("#lookanglesbox .elevation").text("Elevation: " + roundNumber(look.elevation, 2) + "°");

    var range_string = "";
    if(offline.get('opt_imperial')) {
        range_string =  Math.round(look.range * 0.000621371192) + " miles";
    } else {
        range_string = (look.range < 10000) ? Math.round(look.range) + "m" : (Math.round(look.range/100)/10) + " km";
    }
    $("#lookanglesbox .range").text(range_string);
}

function makeQuad(x, y, zoom) {
    var quad = "";
    for (var i = zoom; i > 0; i--) {
      var mask = 1 << (i - 1);
      var cell = 0;
      if ((x & mask) !== 0) cell++;
      if ((y & mask) !== 0) cell += 2;
      quad += cell;
    }
    return quad;
}

// wraps tiles horizontally, returns x
function wrapTiles(x, zoom) {
    var n = Math.pow(2,zoom);
    return (x<0) ? (n+(x%n))%n : x%n;
}

// map type list
// format: [ name, attr, minZoom, maxZoom, getTileUrl function ]
var maptypes = {
    bing_os: [
        'Ordnance Survey (UK)',
        'Bing.com & Ordnance Survey',
         10,
         17,
         function(xy,z) { return 'http://ecn.t'+((Math.abs(xy.x+xy.y)%3)+1)+'.tiles.virtualearth.net/tiles/r'+makeQuad(xy.x, xy.y, z)+'?g=2689&lbl=l1&productSet=mmOS'; }
    ],
    osm: [
        'OSM',
        'OpenStreetMaps.org',
         1,
         19,
         function(xy,z) { var n = Math.pow(2,z); return (xy.y<0 || xy.y>=n) ? null : 'http://'+['a','b','c'][Math.abs(xy.x+xy.y)%3]+'.tile.openstreetmap.org/'+z+'/'+wrapTiles(xy.x,z)+'/'+xy.y+'.png'; }
    ],
    dark_matter: [
        'Dark Matter',
        '&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors, &copy; <a href="http://cartodb.com/attributions">CartoDB</a>',
         1,
         19,
         function(xy,z) { var n = Math.pow(2,z); return (xy.y<0 || xy.y>=n) ? null : 'http://'+['a','b','c'][Math.abs(xy.x+xy.y)%3]+'.basemaps.cartocdn.com/dark_all/'+z+'/'+wrapTiles(xy.x,z)+'/'+xy.y+'.png'; }
    ],
    osm_toner: [
        'OSM Toner',
        'Stamen.org Toner',
         1,
         18,
         function(xy,z) { var n = Math.pow(2,z); return (xy.y<0 || xy.y>=n) ? null : 'http://'+['a','b','c','d'][Math.abs(xy.x+xy.y)%4]+'.tile.stamen.com/toner/'+z+'/'+wrapTiles(xy.x,z)+'/'+xy.y+'.png'; }
    ],
    osm_watercolor: [
        'OSM Watercolor',
        'Stamen.org Watercolor',
         1,
         18,
         function(xy,z) { var n = Math.pow(2,z); return (xy.y<0 || xy.y>=n) ? null : 'http://'+['a','b','c','d'][Math.abs(xy.x+xy.y)%4]+'.tile.stamen.com/watercolor/'+z+'/'+wrapTiles(xy.x,z)+'/'+xy.y+'.png'; }
    ]
};

// generate a list of names for the UI
var maptype_ids = ["roadmap","satellite","terrain"];
for(var i in maptypes) maptype_ids.push(i);


// mousemove event throttle hack for smoother maps pan on firefox and IE
// taken from: http://stackoverflow.com/questions/22306130/how-to-limit-google-maps-api-lag-when-panning-the-map-with-lots-of-markers-and-p

var mthrottle_last = {
                        time : 0,     // last time we let an event pass.
                        x    : -100,  // last x position af the event that passed.
                        y    : -100   // last y position af the event that passed.
                     };
var mthrottle_period = 16;   // ms - don't let pass more than one event every 100ms.
var mthrottle_space  = 40;    // px - let event pass if distance between the last and
                              //      current position is greater than 2 px.

function throttle_events(event) {
    var now = window.performance.now();
    var distance = Math.sqrt(Math.pow(event.clientX - mthrottle_last.x, 2) + Math.pow(event.clientY - mthrottle_last.y, 2));
    var time = now - mthrottle_last.time;
    if (distance * time < mthrottle_space * mthrottle_period) {    //event arrived too soon or mouse moved too little or both
        if (event.stopPropagation) { // W3C/addEventListener()
            event.stopPropagation();
        } else { // Older IE.
            event.cancelBubble = true;
        }
    } else {
        mthrottle_last.time = now;
        mthrottle_last.x    = event.clientX;
        mthrottle_last.y    = event.clientY;
    }
}


function clean_refresh(text, force, history_step) {
    force = !!force;
    history_step = !!history_step;

    if(text == wvar.mode && !force) return false;
    if(ajax_inprogress) return false;

    stopAjax();

    // reset mode if, invalid mode is specified
    if(modeList.indexOf(text) == -1) text = (is_mobile) ? modeDefaultMobile : modeDefault;

    wvar.mode = text;
    tmpC.select(text);

    position_id = 0;

    mapInfoBox.close();

    // clear vehicles
    var callsign;
    for(callsign in vehicles) {
        removePrediction(callsign);
        vehicles[callsign].kill();
    }

    // clear hysplit
    for(callsign in hysplit) {
        hysplit[callsign].setMap(null);
    }

    car_index = 0;
    balloon_index = 0;
    stopFollow(force);

    // add loading spinner in the vehicle list
    $('#main .empty').parent().remove();
    $("#main .portrait,#main .landscape").append(
        '<div class="row vehicle'+elm_uuid+'"><div class="header empty">' +
        '<img style="width:90px;height:30px" src="img/hab-spinner.gif"/></div></div>'
    );
    listScroll.refresh();

    lhash_update(history_step);
    refresh();
    refreshPredictions();

    return true;
}

var tmpC;

function load() {
    //initialize map object
    map = new google.maps.Map(document.getElementById('map'), {
        zoom: 5,
        center: new google.maps.LatLng(53.467511,-2.2338940),
        mapTypeId: google.maps.MapTypeId.ROADMAP,
        mapTypeControlOptions: {
            mapTypeIds: maptype_ids,
            style: google.maps.MapTypeControlStyle.DROPDOWN_MENU
        },
        keyboardShortcuts: false,
        streetViewControl: false,
        rotateControl: false,
        panControl: false,
        scaleControl: true,
        zoomControl: true,
        zoomControlOptions: {
            style: google.maps.ZoomControlStyle.DEFAULT
        },
        scrollwheel: true
    });

    if(window.performance && window.performance.now && window.navigator.userAgent.indexOf("Firefox") != -1) {
        document.getElementById('map').addEventListener("mousemove", throttle_events, true);
    }

    // register custom map types
    for(var i in maptypes) {
        map.mapTypes.set(i, new google.maps.ImageMapType({
            name: maptypes[i][0],
            minZoom: maptypes[i][2],
            maxZoom: maptypes[i][3],
            getTileUrl: maptypes[i][4],
            tileSize: new google.maps.Size(256, 256),
        }));
    }

    var customTileAttr = new google.maps.StatusTextControl({
        text: '',
        map: map,
        position: google.maps.ControlPosition.BOTTOM_RIGHT,
    });

    google.maps.event.addListener(map, 'maptypeid_changed', function() {
        var id = this.getMapTypeId();
        customTileAttr.setText((id in maptypes) ? maptypes[id][1] : '');
    });

    // update current position if we geolocation is available
    if(currentPosition) updateCurrentPosition(currentPosition.lat, currentPosition.lon);

    // initalize nite overlay
    nite.init(map);
    if(!offline.get('opt_daylight')) nite.hide();
    setInterval(function() { nite.refresh(); }, 60000); // 1min

    // we need a dummy overlay to access getProjection()
    overlay = new google.maps.OverlayView();
    overlay.draw = function() {};
    overlay.setMap(map);

    // status message boxes
    var statusElm = new google.maps.StatusTextControl({
        map: map,
        position: google.maps.ControlPosition.RIGHT_BOTTOM,
        text: "<span id='stText'></span><span> Updated: </span><i class='friendly-dtime' id='stTimer'>never</i>"
    });


    google.maps.event.addListener(map, 'zoom_changed', function() {
        updateZoom();
    });

    google.maps.event.addListener(map, 'dragstart', function() {
        if(!wvar.embeded) manual_pan = true;
    });

    // only start population the map, once its completely loaded
    google.maps.event.addListenerOnce(map, 'idle', function(){
        load_hash(null);

        // initialize period menu
        tmpC = new google.maps.DropDownControl({
            map: map,
            title: "Show activity for given period",
            //position: google.maps.ControlPosition.TOP_RIGHT,
            position: google.maps.ControlPosition.RIGHT_TOP,
            headerPrefix: "Last: ",
            list: modeList,
            listDefault: modeList.indexOf(wvar.mode),
            callback: clean_refresh,
        });

        google.maps.event.addListener(map, 'idle', function() {
            lhash_update();
        });
        google.maps.event.addListener(map, 'maptypeid_changed', function() {
            lhash_update();
        });

        startAjax();
    });

    // animate-in the timebox,
    setTimeout(function() {
        var elm = $("#timebox");

        if(is_mobile) $(".slickbox").css({left:'5px'});
        var origW = elm.width();
        var iconW = elm.find("svg").width();

        if(offline.get('opt_hide_timebox')) {
            elm.removeClass('animate').hide();
            $("#lookanglesbox").css({top:'7px'});
        }

        // prep for animation
        $(".slickbox.animate").css({width:iconW}).find("span").hide();

        if(!offline.get('opt_hide_timebox')) {
            // animate timebox
            elm.fadeIn(500,"easeOut").animate({width:origW},400,"easeOut", function() {
              $("#timebox span").fadeIn(500, "easeOut");
            });
        }

        // animate lookanglesbox, delayed start by 300ms
        $("#lookanglesbox").delay(200).fadeIn(500,"easeOut").animate({width:origW},400,"easeOut", function() {
          if(GPS_ts === null) {
              $("#lookanglesbox .nopos").fadeIn(500, "easeOut");
          } else if($("#lookanglesbox span:first").is(":hidden")) {
              $("#lookanglesbox .nofollow").fadeIn(500, "easeOut");
          }
        });

        // if we there is enough screen space open aboutbox on startup
        if(!is_mobile && !offline.get('opt_nowelcome') && $(window).width() > 900) $('.nav li.about').click();

    }, 500);

    // initialize clouds layer
    layers_clouds = new google.maps.weather.CloudLayer();
    if(offline.get('opt_layers_clouds')) layers_clouds.setMap(map);

    // load if aprs layer, if selected
    if(offline.get('opt_layers_aprs')) map.overlayMapTypes.setAt("1", overlayAPRS);
}

function panTo(vcallsign) {
    if(!vcallsign || vehicles[vcallsign] === undefined) return;

    // update lookangles
    update_lookangles(vcallsign);

    // pan map
    //if(vehicles[vcallsign].marker_shadow) map.panTo(vehicles[vcallsign].marker_shadow.getPosition());
    //else map.panTo(vehicles[vcallsign].marker.getPosition());
    map.panTo(vehicles[vcallsign].marker.getPosition());
}

function title_case(s) {
  return s.replace(/\w\S*/g, function(txt) {
    return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
  });
}

function guess_name(key) {
  return title_case(key.replace(/_/g, " "));
{'fraction_non_alphanumeric': 0.16942805571270778, 'fraction_numerical': 0.02413858131953986, 'mean_word_length': 2.5369223439733206, 'pattern_counts': {'":': 4, '<': 69, '<?xml version=': 0, '>': 57, 'https://': 0, 'lorem ipsum': 0, 'www.': 12, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 24, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '860393', 'n_tokens_mistral': 13596, 'n_tokens_neox': 12759, 'n_words': 2624}