cusf-standalone-predictor/predict/js/pred.js

459 wiersze
17 KiB
JavaScript

/*
* CUSF Landing Prediction Version 2
* Jon Sowman 2010
* jon@hexoc.com
* http://www.hexoc.com
*
* http://github.com/jonsowman/cusf-standalone-predictor
*
*/
// This function runs when the document object model is fully populated
// and the page is loaded
$(document).ready(function() {
// Initialise the map canvas with parameters (lat, long, zoom-level)
initMap(52, 0, 8);
// Populate the launch site list from sites.json
populateLaunchSite();
// Setup all event handlers in the UI using jQuery
setupEventHandlers();
// Initialise UI elements such as draggable windows
initUI();
// Check if an old prediction is to be displayed, and process if so
displayOld();
// Plot the initial launch location
plotClick();
// Initialise the burst calculator
calc_init();
});
// See if an old UUID was supplied in the hashstring
// If it was, extract it, then populate the launch card with its parameters
// then display the prediction
function displayOld() {
// Are we trying to display an old prediction?
if( window.location.hash != "" ) {
var ln = window.location.hash.split("=");
var posteq = ln[1];
if ( posteq.length != 40 ) {
throwError("The supplied hashstring was not a valid UUID.");
appendDebug("The hashstring was not the expected length");
} else {
current_uuid = posteq;
appendDebug("Got an old UUID to plot:<br>" + current_uuid);
appendDebug("Trying to populate form with scenario data...");
populateFormByUUID(current_uuid);
appendDebug("Trying to get progress JSON");
$.getJSON("preds/"+current_uuid+"/progress.json",
function(progress) {
appendDebug("Got progress JSON from server for UUID");
if ( progress['error'] || !progress['pred_complete'] ) {
appendDebug("The prediction was not completed"
+ " correctly, quitting");
} else {
appendDebug("JSON said the prediction completed "
+ "without errors");
writePredictionInfo(current_uuid,
progress['run_time'],
progress['gfs_timestamp']);
getCSV(current_uuid);
}
});
}
}
}
// A prediction has just been requested, so initialise the progress bar
// and fade in the prediction progress window
function predSub() {
appendDebug(null, 1); // clear debug window
appendDebug("Sending data to server...");
// Initialise progress bar
$("#prediction_progress").progressbar({ value: 0 });
$("#prediction_status").html("Sending data to server...");
$("#status_message").fadeIn(250);
}
// Make an AJAX request to the server and get the scenario information
// for a given UUID, then populate the launch card with it
function populateFormByUUID(pred_uuid) {
$.get("ajax.php", { "action":"getModelByUUID", "uuid":pred_uuid }, function(data) {
if ( !data.valid ) {
appendDebug("Populating form by UUID failed");
appendDebug("The server said the model it made was invalid");
} else {
// we're good to go, populate the form
$("#lat").val(data.latitude);
$("#lon").val(data.longitude);
$("#initial_alt").val(data.altitude);
$("#hour").val(data.hour);
// we need to make minutes be "04" instead of "4"
var scenario_minute = data.minute;
if ( scenario_minute < 10 ) scenario_minute = "0" + scenario_minute;
$("#min").val(scenario_minute);
$("#second").val(data.second);
$("#day").val(data.day);
$("#month").attr("selectedIndex", data.month-1);
$("#year").val(data.year);
// we have to use [] notation for
// values that have -s in them
$("#ascent").val(data['ascent-rate']);
$("#drag").val(data['descent-rate']);
$("#burst").val(data['burst-altitude']);
$("#software").val(data.software);
$("#delta_lat").val(data['lat-delta']);
$("#delta_lon").val(data['lon-delta']);
// now sort the map out
SetSiteOther();
plotClick();
}
}, 'json');
}
// Add information to the hashstring of the current window
function addHashLink(link) {
var ln = "#!/" + link;
window.location = ln;
}
// Clear the Launch Site dropdown and repopulate it with the information from
// sites.json, as well as an "Other" option to open the saved locations window
function populateLaunchSite() {
$("#site > option").remove();
$.getJSON("sites.json", function(sites) {
$.each(sites, function(sitename, site) {
$("<option>").attr("value", sitename).text(sitename).appendTo("#site");
});
$("<option>").attr("value", "Other").text("Other").appendTo("#site");
return true;
});
return true;
}
// The onchange handler for the launch locations dropdown menu, which opens
// the saved locations window if "Other" was chosen; sets the launch card
// lat/lon and plots the new launch location otherwise
function changeLaunchSite() {
var selectedName = $("#site").val();
if ( selectedName == "Other" ) {
appendDebug("User requested locally saved launch sites");
if ( constructCookieLocationsTable("cusf_predictor") ) {
$("#location_save_local").fadeIn();
}
} else {
$.getJSON("sites.json", function(sites) {
$.each(sites, function(sitename, site) {
if ( selectedName == sitename ) {
$("#lat").val(site.latitude);
$("#lon").val(site.longitude);
$("#initial_alt").val(site.altitude);
}
});
plotClick();
});
}
}
// Populate and enable the download CSV, KML and Pan To links, and write the
// time the prediction was run and the model used to the Scenario Info window
function writePredictionInfo(current_uuid, run_time, gfs_timestamp) {
// populate the download links
$("#dlcsv").attr("href", "preds/"+current_uuid+"/flight_path.csv");
$("#dlkml").attr("href", "kml.php?uuid="+current_uuid);
$("#panto").click(function() {
map.panTo(map_items['launch_marker'].position);
//map.setZoom(7);
});
$("#run_time").html(POSIXtoHM(run_time, "H:i d/m/Y"));
$("#gfs_timestamp").html(gfs_timestamp);
}
// Hide the launch card and scenario information windows, then fade out the
// map before setting an interval to poll for prediction progress
function handlePred(pred_uuid) {
$("#prediction_status").html("Searching for wind data...");
$("#input_form").hide("slide", { direction: "down" }, 500);
$("#scenario_info").hide("slide", { direction: "up" }, 500);
// disable user control of the map canvas
$("#map_canvas").fadeTo(1000, 0.2);
// ajax to poll for progress
ajaxEventHandle = setInterval("getJSONProgress('"
+ pred_uuid + "')", stdPeriod);
}
// Get the CSV for a UUID and then pass it to the parseCSV() function
function getCSV(pred_uuid) {
$.get("ajax.php", { "action":"getCSV", "uuid":pred_uuid }, function(data) {
if(data != null) {
appendDebug("Got JSON response from server for flight path,"
+ " parsing...");
if (parseCSV(data) ) {
appendDebug("Parsing function returned successfully.");
appendDebug("Done, AJAX functions quitting.");
} else {
appendDebug("The parsing function failed.");
}
} else {
appendDebug("Server couldn't find a CSV for that UUID");
throwError("Sorry, we couldn't find the data for that UUID. "+
"Please run another prediction.");
}
}, 'json');
}
// Called at set inervals to examine the progress.json file on the server for
// a UUID to check for progress, and update the progress window
// Also handles high latency connections by increasing the timeout before
// the AJAX request completes and decreasing polling interval
function getJSONProgress(pred_uuid) {
$.ajax({
url:"preds/"+pred_uuid+"/progress.json",
dataType:'json',
timeout: ajaxTimeout,
error: function(xhr, status, error) {
if ( status == "timeout" ) {
appendDebug("Polling for progress JSON timed out");
// check that we haven't reached maximum allowed timeout
if ( ajaxTimeout < maxAjaxTimeout ) {
// if not, add the delta to the timeout value
newTimeout = ajaxTimeout + deltaAjaxTimeout;
appendDebug("Increasing AJAX timeout from " + ajaxTimeout
+ "ms to " + newTimeout + "ms");
ajaxTimeout = newTimeout;
} else if ( ajaxTimeout != hlTimeout ) {
// otherwise, increase poll delay and timeout
appendDebug("Reached maximum ajaxTimeout value of "
+ maxAjaxTimeout);
clearInterval(ajaxEventHandle);
appendDebug("Switching to high latency mode");
appendDebug("Setting polling interval to "+hlPeriod+"ms");
appendDebug("Setting progress JSON timeout to "
+ hlTimeout + "ms");
ajaxTimeout = hlTimeout;
ajaxEventHandle = setInterval("getJSONProgress('"
+ pred_uuid + "')", hlPeriod);
}
}
},
success: processProgress
});
}
// The contents of progress.json are given to this function to process
// If the prediction has completed, reset the GUI and display the new
// prediction; otherwise update the progress window
function processProgress(progress) {
if ( progress['error'] ) {
clearInterval(ajaxEventHandle);
appendDebug("There was an error in running the prediction: "
+ progress['error']);
} else {
// get the progress of the wind data
if ( progress['gfs_complete'] == true ) {
if ( progress['pred_complete'] == true ) { // pred has finished
$("#prediction_status").html("Prediction finished.");
appendDebug("Server says: the predictor finished running.");
appendDebug("Attempting to retrieve flight path from server");
// reset the GUI
resetGUI();
// stop polling for JSON
clearInterval(ajaxEventHandle);
// parse the data
getCSV(current_uuid);
appendDebug("Server gave a prediction run timestamp of "
+ progress['run_time']);
appendDebug("Server said it used the "
+ progress['gfs_timestamp'] + " GFS model");
writePredictionInfo(current_uuid, progress['run_time'],
progress['gfs_timestamp']);
addHashLink("uuid="+current_uuid);
} else if ( progress['pred_running'] != true ) {
$("#prediction_status").html("Waiting for predictor to run...");
appendDebug("Server says: predictor not yet running...");
} else if ( progress['pred_running'] == true ) {
$("#prediction_status").html("Predictor running...");
appendDebug("Server says: predictor currently running");
}
} else {
$("#prediction_status").html("Downloading wind data");
$("#prediction_progress").progressbar("option", "value",
progress['gfs_percent']);
$("#prediction_percent").html(progress['gfs_percent'] +
"% - Estimated time remaining: "
+ progress['gfs_timeremaining']);
appendDebug("Server says: downloaded " +
progress['gfs_percent'] + "% of GFS files");
}
}
return true;
}
// Once a flight path has been returned from the server, this function takes
// an array where each elemt is a line of that file
// Constructs the path, plots the launch/land/burst markers, writes the
// prediction information to the scenario information window and then plots
// the delta square
function parseCSV(lines) {
if( lines.length <= 0 ) {
appendDebug("The server returned an empty CSV file");
return false;
}
var path = [];
var max_height = -10; //just any -ve number
var max_point = null;
var launch_lat;
var launch_lon;
var land_lat;
var land_lon;
var launch_pt;
var land_pt;
var burst_lat;
var burst_lon;
var burst_pt;
var burst_time;
var launch_time;
var land_time;
$.each(lines, function(idx, line) {
entry = line.split(',');
// Check for a valid entry length
if(entry.length >= 4) {
var point = new google.maps.LatLng( parseFloat(entry[1]),
parseFloat(entry[2]) );
// Get launch lat/lon
if ( idx == 0 ) {
launch_lat = entry[1];
launch_lon = entry[2];
launch_time = entry[0];
launch_pt = point;
}
// Set on every iteration such that last valid entry gives the
// landing position
land_lat = entry[1];
land_lon = entry[2];
land_time = entry[0];
land_pt = point;
// Find the burst lat/lon/alt
if( parseFloat(entry[3]) > max_height ) {
max_height = parseFloat(entry[3]);
burst_pt = point;
burst_lat = entry[1];
burst_lon = entry[2];
burst_time = entry[0];
}
// Push the point onto the polyline path
path.push(point);
}
});
appendDebug("Flight data parsed, creating map plot...");
clearMapItems();
// Calculate range and time of flight
var range = distHaversine(launch_pt, land_pt, 1);
var flighttime = land_time - launch_time;
var f_hours = Math.floor((flighttime % 86400) / 3600);
var f_minutes = Math.floor(((flighttime % 86400) % 3600) / 60);
if ( f_minutes < 10 ) f_minutes = "0"+f_minutes;
flighttime = f_hours + "hr" + f_minutes;
$("#cursor_pred_range").html(range);
$("#cursor_pred_time").html(flighttime);
$("#cursor_pred").show();
// Make some nice icons
var launch_icon = new google.maps.MarkerImage(launch_img,
new google.maps.Size(16,16),
new google.maps.Point(0, 0),
new google.maps.Point(8, 8)
);
var land_icon = new google.maps.MarkerImage(land_img,
new google.maps.Size(16,16),
new google.maps.Point(0, 0),
new google.maps.Point(8, 8)
);
var launch_marker = new google.maps.Marker({
position: launch_pt,
map: map,
icon: launch_icon,
title: 'Balloon launch ('+launch_lat+', '+launch_lon+') at '
+ POSIXtoHM(launch_time) + "UTC"
});
var land_marker = new google.maps.Marker({
position: land_pt,
map:map,
icon: land_icon,
title: 'Predicted Landing ('+land_lat+', '+land_lon+') at '
+ POSIXtoHM(land_time) + "UTC"
});
var path_polyline = new google.maps.Polyline({
path:path,
map: map,
strokeColor: '#000000',
strokeWeight: 3,
strokeOpacity: 0.75
});
var pop_marker = new google.maps.Marker({
position: burst_pt,
map: map,
icon: burst_img,
title: 'Balloon burst (' + burst_lat + ', ' + burst_lon
+ ' at altitude ' + max_height + 'm) at '
+ POSIXtoHM(burst_time) + "UTC"
});
// Add the launch/land markers to map
// We might need access to these later, so push them associatively
map_items['launch_marker'] = launch_marker;
map_items['land_marker'] = land_marker;
map_items['pop_marker'] = pop_marker;
map_items['path_polyline'] = path_polyline;
// We wiped off the old delta square,
// And it may have changed anyway, so re-plot
drawDeltaSquare(map);
// Pan to the new position
map.panTo(launch_pt);
map.setZoom(8);
return true;
}
// Return the size of a given associative array
function getAssocSize(arr) {
var i = 0;
for ( j in arr ) {
i++;
}
return i;
}
function POSIXtoHM(timestamp, format) {
// using JS port of PHP's date()
var ts = new Date();
ts.setTime(timestamp*1000);
// account for DST
if ( ts.format("I") == 1 ) {
ts.setTime((timestamp-3600)*1000);
}
if ( format == null || format == "" ) format = "H:i";
var str = ts.format(format);
return str;
}
rad = function(x) {return x*Math.PI/180;}