├── favicon.ico ├── images ├── arrow.png ├── csv.gif ├── kml.png ├── tipsy.gif ├── balloon.png ├── balloon-sm.png ├── pop-marker.png ├── target-1.png ├── target-10.png ├── target-11.png ├── target-12.png ├── target-13.png ├── target-14.png ├── target-15.png ├── target-16.png ├── target-17.png ├── target-18.png ├── target-2.png ├── target-3.png ├── target-4.png ├── target-5.png ├── target-6.png ├── target-7.png ├── target-8.png ├── target-9.png ├── target-red.png ├── drag_handle.png ├── target-1-sm.png ├── target-8-sm.png ├── target-blue.png ├── marker-sm-black.png ├── marker-sm-red.png └── target-yellow.png ├── css ├── images │ ├── layers.png │ ├── layers-2x.png │ ├── marker-icon.png │ ├── marker-shadow.png │ ├── marker-icon-2x.png │ ├── ui-icons_444444_256x240.png │ ├── ui-icons_555555_256x240.png │ └── ui-icons_777777_256x240.png ├── predictor.css └── leaflet.css ├── test.py ├── js ├── pred │ ├── pred-config.js │ ├── pred-cookie.js │ ├── pred-ui.js │ ├── pred-event.js │ ├── pred-map.js │ ├── pred.js │ └── pred-new.js ├── Leaflet.Control.Custom.js ├── jquery.tipsy.js ├── calc │ └── calc.js ├── jquery.form.js ├── jquery.jookie.js └── colour-map.js ├── sites.json ├── README.md └── index.html /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/projecthorus/leaflet_predictor/HEAD/favicon.ico -------------------------------------------------------------------------------- /images/arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/projecthorus/leaflet_predictor/HEAD/images/arrow.png -------------------------------------------------------------------------------- /images/csv.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/projecthorus/leaflet_predictor/HEAD/images/csv.gif -------------------------------------------------------------------------------- /images/kml.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/projecthorus/leaflet_predictor/HEAD/images/kml.png -------------------------------------------------------------------------------- /images/tipsy.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/projecthorus/leaflet_predictor/HEAD/images/tipsy.gif -------------------------------------------------------------------------------- /images/balloon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/projecthorus/leaflet_predictor/HEAD/images/balloon.png -------------------------------------------------------------------------------- /css/images/layers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/projecthorus/leaflet_predictor/HEAD/css/images/layers.png -------------------------------------------------------------------------------- /images/balloon-sm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/projecthorus/leaflet_predictor/HEAD/images/balloon-sm.png -------------------------------------------------------------------------------- /images/pop-marker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/projecthorus/leaflet_predictor/HEAD/images/pop-marker.png -------------------------------------------------------------------------------- /images/target-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/projecthorus/leaflet_predictor/HEAD/images/target-1.png -------------------------------------------------------------------------------- /images/target-10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/projecthorus/leaflet_predictor/HEAD/images/target-10.png -------------------------------------------------------------------------------- /images/target-11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/projecthorus/leaflet_predictor/HEAD/images/target-11.png -------------------------------------------------------------------------------- /images/target-12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/projecthorus/leaflet_predictor/HEAD/images/target-12.png -------------------------------------------------------------------------------- /images/target-13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/projecthorus/leaflet_predictor/HEAD/images/target-13.png -------------------------------------------------------------------------------- /images/target-14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/projecthorus/leaflet_predictor/HEAD/images/target-14.png -------------------------------------------------------------------------------- /images/target-15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/projecthorus/leaflet_predictor/HEAD/images/target-15.png -------------------------------------------------------------------------------- /images/target-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/projecthorus/leaflet_predictor/HEAD/images/target-16.png -------------------------------------------------------------------------------- /images/target-17.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/projecthorus/leaflet_predictor/HEAD/images/target-17.png -------------------------------------------------------------------------------- /images/target-18.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/projecthorus/leaflet_predictor/HEAD/images/target-18.png -------------------------------------------------------------------------------- /images/target-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/projecthorus/leaflet_predictor/HEAD/images/target-2.png -------------------------------------------------------------------------------- /images/target-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/projecthorus/leaflet_predictor/HEAD/images/target-3.png -------------------------------------------------------------------------------- /images/target-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/projecthorus/leaflet_predictor/HEAD/images/target-4.png -------------------------------------------------------------------------------- /images/target-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/projecthorus/leaflet_predictor/HEAD/images/target-5.png -------------------------------------------------------------------------------- /images/target-6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/projecthorus/leaflet_predictor/HEAD/images/target-6.png -------------------------------------------------------------------------------- /images/target-7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/projecthorus/leaflet_predictor/HEAD/images/target-7.png -------------------------------------------------------------------------------- /images/target-8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/projecthorus/leaflet_predictor/HEAD/images/target-8.png -------------------------------------------------------------------------------- /images/target-9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/projecthorus/leaflet_predictor/HEAD/images/target-9.png -------------------------------------------------------------------------------- /images/target-red.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/projecthorus/leaflet_predictor/HEAD/images/target-red.png -------------------------------------------------------------------------------- /images/drag_handle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/projecthorus/leaflet_predictor/HEAD/images/drag_handle.png -------------------------------------------------------------------------------- /images/target-1-sm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/projecthorus/leaflet_predictor/HEAD/images/target-1-sm.png -------------------------------------------------------------------------------- /images/target-8-sm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/projecthorus/leaflet_predictor/HEAD/images/target-8-sm.png -------------------------------------------------------------------------------- /images/target-blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/projecthorus/leaflet_predictor/HEAD/images/target-blue.png -------------------------------------------------------------------------------- /css/images/layers-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/projecthorus/leaflet_predictor/HEAD/css/images/layers-2x.png -------------------------------------------------------------------------------- /css/images/marker-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/projecthorus/leaflet_predictor/HEAD/css/images/marker-icon.png -------------------------------------------------------------------------------- /images/marker-sm-black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/projecthorus/leaflet_predictor/HEAD/images/marker-sm-black.png -------------------------------------------------------------------------------- /images/marker-sm-red.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/projecthorus/leaflet_predictor/HEAD/images/marker-sm-red.png -------------------------------------------------------------------------------- /images/target-yellow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/projecthorus/leaflet_predictor/HEAD/images/target-yellow.png -------------------------------------------------------------------------------- /css/images/marker-shadow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/projecthorus/leaflet_predictor/HEAD/css/images/marker-shadow.png -------------------------------------------------------------------------------- /css/images/marker-icon-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/projecthorus/leaflet_predictor/HEAD/css/images/marker-icon-2x.png -------------------------------------------------------------------------------- /css/images/ui-icons_444444_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/projecthorus/leaflet_predictor/HEAD/css/images/ui-icons_444444_256x240.png -------------------------------------------------------------------------------- /css/images/ui-icons_555555_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/projecthorus/leaflet_predictor/HEAD/css/images/ui-icons_555555_256x240.png -------------------------------------------------------------------------------- /css/images/ui-icons_777777_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/projecthorus/leaflet_predictor/HEAD/css/images/ui-icons_777777_256x240.png -------------------------------------------------------------------------------- /test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | from http.server import HTTPServer, SimpleHTTPRequestHandler, test 3 | import sys 4 | 5 | class CORSRequestHandler (SimpleHTTPRequestHandler): 6 | def end_headers (self): 7 | self.send_header('Access-Control-Allow-Origin', '*') 8 | SimpleHTTPRequestHandler.end_headers(self) 9 | 10 | if __name__ == '__main__': 11 | test(CORSRequestHandler, HTTPServer, port=int(sys.argv[1]) if len(sys.argv) > 1 else 8000) -------------------------------------------------------------------------------- /js/pred/pred-config.js: -------------------------------------------------------------------------------- 1 | /* 2 | * CUSF Landing Prediction Version 2 3 | * http://www.cuspaceflight.co.uk 4 | * 5 | * Jon Sowman 2010 6 | * jon@hexoc.com 7 | * http://www.hexoc.com 8 | * 9 | * http://github.com/jonsowman/cusf-standalone-predictor 10 | * 11 | */ 12 | 13 | // JavaScript configuration options in this file 14 | // To keep the index page JS-free 15 | 16 | var ajaxEventHandle, showStatusEventHandle, firstJSONProgressHandle, cursorPredHideHandle; 17 | var current_uuid = '0'; 18 | 19 | var map; 20 | var kmlLayer = null; 21 | var map_items = []; 22 | var time_was_now = false; 23 | var hourly_predictions = {}; 24 | var hourly_mode = false; 25 | var hourly_polyline = null; 26 | var launch_img = "images/target-1-sm.png"; 27 | var land_img = "images/target-8-sm.png"; 28 | var burst_img = "images/pop-marker.png"; 29 | var clickListener; 30 | var clickMarker; 31 | 32 | // Polling progress parameters 33 | var ajaxTimeout = 500; 34 | var maxAjaxTimeout = 2000; 35 | var deltaAjaxTimeout = 500; 36 | var firstAjaxDelay = 100; // the predictor runs really fast now 37 | var showStatusDelay = 100; // if the server responds fast, don't bother fading out 38 | var stdPeriod = 2000; // standard 39 | var hlPeriod = 10000; // high latency 40 | var hlTimeout = 5000; // high latency 41 | -------------------------------------------------------------------------------- /sites.json: -------------------------------------------------------------------------------- 1 | { 2 | "Auburn": { 3 | "altitude" : 306, 4 | "latitude" : -34.0297, 5 | "longitude" : 138.6917 6 | }, 7 | "Churchill": { 8 | "latitude" : 52.2135, 9 | "longitude" : 0.0964, 10 | "altitude" : 0 11 | }, 12 | "Elsworth": { 13 | "latitude" : 52.2511, 14 | "longitude" : -0.0927, 15 | "altitude" : 0 16 | }, 17 | "Adelaide": { 18 | "altitude" : 0, 19 | "latitude" : -34.9499, 20 | "longitude" : 138.5194 21 | }, 22 | "Tullamarine": { 23 | "altitude": 0, 24 | "latitude": -37.6656, 25 | "longitude": 144.8319 26 | }, 27 | "Boston Spa": { 28 | "latitude" : 53.8997, 29 | "longitude" : -1.3629, 30 | "altitude" : 0 31 | }, 32 | "Mt Barker": { 33 | "altitude" : 360, 34 | "latitude" : -35.0765, 35 | "longitude" : 138.8569 36 | }, 37 | "Cookstown": { 38 | "altitude" : 60, 39 | "latitude" : 54.632162, 40 | "longitude" : -6.757982 41 | }, 42 | "Preston St Mary": { 43 | "latitude" : 52.1215, 44 | "longitude" : 0.8078, 45 | "altitude" : 70 46 | }, 47 | "Ross-on-Wye": { 48 | "altitude" : 150, 49 | "latitude" : 51.95023, 50 | "longitude" : -2.5445 51 | }, 52 | "Ararat": { 53 | "altitude" : 295, 54 | "latitude" : -37.294, 55 | "longitude" : 142.9374 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CUSF Landing Predictor - Tawhiri / Leaflet Version 2 | 3 | Cambridge University Spaceflight landing predictor - a web-based tool for predicting the flight path and landing location of latex meteorological sounding balloons. 4 | 5 | This fork of the [original predictor](https://github.com/jonsowman/cusf-standalone-predictor) contains a continuation of the original CUSF predictor, which utilises the [Tawhiri API](https://github.com/projecthorus/tawhiri/), and also uses Leaflet for mapping instead of Google Maps. 6 | 7 | A live version of the predictor intended for non-commercial use is available at http://predict.sondehub.org/ , hosted by the [Sondehub project](https://github.com/projecthorus/sondehub-infra/wiki). 8 | 9 | If hosting this yourself, please note that it uses the Sondehub-hosted instance of Tawhiri. Please [contact us](https://github.com/projecthorus/sondehub-infra/wiki#contacts) to discuss fair usage of this API. 10 | 11 | ## License 12 | 13 | This work is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or any later version. This work is distributed in the hope that it will be useful, but without any warranty; without even the implied warranty of merchantability or fitness for a particular purpose. 14 | 15 | ## Credits & Acknowledgments 16 | Credit as detailed in individual files, but notably: 17 | 18 | * Rich Wareham - The new predictor and the hourly predictor system 19 | * Fergus Noble, Ed Moore and many others 20 | * Adam Grieg 21 | * Jon Sowman 22 | 23 | Work to switch the predictor across to Tawhiri, and addition of other features by: 24 | * Mark Jessop - 25 | 26 | -------------------------------------------------------------------------------- /js/Leaflet.Control.Custom.js: -------------------------------------------------------------------------------- 1 | (function (window, document, undefined) { 2 | L.Control.Custom = L.Control.extend({ 3 | version: '1.0.1', 4 | options: { 5 | position: 'topright', 6 | id: '', 7 | title: '', 8 | classes: '', 9 | content: '', 10 | style: {}, 11 | datas: {}, 12 | events: {}, 13 | }, 14 | container: null, 15 | onAdd: function (map) { 16 | this.container = L.DomUtil.create('div'); 17 | this.container.id = this.options.id; 18 | this.container.title = this.options.title; 19 | this.container.className = this.options.classes; 20 | this.container.innerHTML = this.options.content; 21 | 22 | for (var option in this.options.style) 23 | { 24 | this.container.style[option] = this.options.style[option]; 25 | } 26 | 27 | for (var data in this.options.datas) 28 | { 29 | this.container.dataset[data] = this.options.datas[data]; 30 | } 31 | 32 | 33 | /* Prevent click events propagation to map */ 34 | L.DomEvent.disableClickPropagation(this.container); 35 | 36 | /* Prevent right click event propagation to map */ 37 | L.DomEvent.on(this.container, 'contextmenu', function (ev) 38 | { 39 | L.DomEvent.stopPropagation(ev); 40 | }); 41 | 42 | /* Prevent scroll events propagation to map when cursor on the div */ 43 | L.DomEvent.disableScrollPropagation(this.container); 44 | 45 | for (var event in this.options.events) 46 | { 47 | L.DomEvent.on(this.container, event, this.options.events[event], this.container); 48 | } 49 | 50 | return this.container; 51 | }, 52 | 53 | onRemove: function (map) { 54 | for (var event in this.options.events) 55 | { 56 | L.DomEvent.off(this.container, event, this.options.events[event], this.container); 57 | } 58 | }, 59 | }); 60 | 61 | L.control.custom = function (options) { 62 | return new L.Control.Custom(options); 63 | }; 64 | 65 | }(window, document)); -------------------------------------------------------------------------------- /js/jquery.tipsy.js: -------------------------------------------------------------------------------- 1 | (function($) { 2 | $.fn.tipsy = function(options) { 3 | 4 | options = $.extend({}, $.fn.tipsy.defaults, options); 5 | 6 | return this.each(function() { 7 | 8 | var opts = $.fn.tipsy.elementOptions(this, options); 9 | 10 | $(this).hover(function() { 11 | 12 | $.data(this, 'cancel.tipsy', true); 13 | 14 | var tip = $.data(this, 'active.tipsy'); 15 | if (!tip) { 16 | tip = $('
'); 17 | tip.css({position: 'absolute', zIndex: 100000}); 18 | $.data(this, 'active.tipsy', tip); 19 | } 20 | 21 | if ($(this).attr('title') || typeof($(this).attr('original-title')) != 'string') { 22 | $(this).attr('original-title', $(this).attr('title') || '').removeAttr('title'); 23 | } 24 | 25 | var title; 26 | if (typeof opts.title == 'string') { 27 | title = $(this).attr(opts.title == 'title' ? 'original-title' : opts.title); 28 | } else if (typeof opts.title == 'function') { 29 | title = opts.title.call(this); 30 | } 31 | 32 | tip.find('.tipsy-inner')[opts.html ? 'html' : 'text'](title || opts.fallback); 33 | 34 | var pos = $.extend({}, $(this).offset(), {width: this.offsetWidth, height: this.offsetHeight}); 35 | tip.get(0).className = 'tipsy'; // reset classname in case of dynamic gravity 36 | tip.remove().css({top: 0, left: 0, visibility: 'hidden', display: 'block'}).appendTo(document.body); 37 | var actualWidth = tip[0].offsetWidth, actualHeight = tip[0].offsetHeight; 38 | var gravity = (typeof opts.gravity == 'function') ? opts.gravity.call(this) : opts.gravity; 39 | 40 | switch (gravity.charAt(0)) { 41 | case 'n': 42 | tip.css({top: pos.top + pos.height, left: pos.left + pos.width / 2 - actualWidth / 2}).addClass('tipsy-north'); 43 | break; 44 | case 's': 45 | tip.css({top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2}).addClass('tipsy-south'); 46 | break; 47 | case 'e': 48 | tip.css({top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth}).addClass('tipsy-east'); 49 | break; 50 | case 'w': 51 | tip.css({top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width}).addClass('tipsy-west'); 52 | break; 53 | } 54 | 55 | if (opts.fade) { 56 | tip.css({opacity: 0, display: 'block', visibility: 'visible'}).animate({opacity: 0.8}); 57 | } else { 58 | tip.css({visibility: 'visible'}); 59 | } 60 | 61 | }, function() { 62 | $.data(this, 'cancel.tipsy', false); 63 | var self = this; 64 | setTimeout(function() { 65 | if ($.data(this, 'cancel.tipsy')) return; 66 | var tip = $.data(self, 'active.tipsy'); 67 | if (opts.fade) { 68 | tip.stop().fadeOut(function() { $(this).remove(); }); 69 | } else { 70 | tip.remove(); 71 | } 72 | }, 100); 73 | 74 | }); 75 | 76 | }); 77 | 78 | }; 79 | 80 | // Overwrite this method to provide options on a per-element basis. 81 | // For example, you could store the gravity in a 'tipsy-gravity' attribute: 82 | // return $.extend({}, options, {gravity: $(ele).attr('tipsy-gravity') || 'n' }); 83 | // (remember - do not modify 'options' in place!) 84 | $.fn.tipsy.elementOptions = function(ele, options) { 85 | return $.metadata ? $.extend({}, options, $(ele).metadata()) : options; 86 | }; 87 | 88 | $.fn.tipsy.defaults = { 89 | fade: false, 90 | fallback: '', 91 | gravity: 'n', 92 | html: false, 93 | title: 'title' 94 | }; 95 | 96 | $.fn.tipsy.autoNS = function() { 97 | return $(this).offset().top > ($(document).scrollTop() + $(window).height() / 2) ? 's' : 'n'; 98 | }; 99 | 100 | $.fn.tipsy.autoWE = function() { 101 | return $(this).offset().left > ($(document).scrollLeft() + $(window).width() / 2) ? 'e' : 'w'; 102 | }; 103 | 104 | })(jQuery); 105 | -------------------------------------------------------------------------------- /js/pred/pred-cookie.js: -------------------------------------------------------------------------------- 1 | /* 2 | * CUSF Landing Prediction Version 2 3 | * Jon Sowman 2010 4 | * jon@hexoc.com 5 | * http://www.hexoc.com 6 | * 7 | * http://github.com/jonsowman/cusf-standalone-predictor 8 | * 9 | * This file contains all the cookie-related functions for the landing 10 | * predictor 11 | * 12 | */ 13 | 14 | function saveLocationToCookie() { 15 | // Get the variables from the form 16 | var req_lat = $("#req_lat").val(); 17 | var req_lon = $("#req_lon").val(); 18 | var req_alt = $("#req_alt").val(); 19 | var req_name = $("#req_name").val(); 20 | var cookie_name = "cusf_predictor"; 21 | var locations_limit = 5; 22 | var name_limit = 20; 23 | 24 | // Check the length of the name 25 | if ( req_name.length > name_limit ) { 26 | req_name = req_name.substr(0, name_limit); 27 | } 28 | 29 | // Now let's init the cookie 30 | $.Jookie.Initialise(cookie_name, 99999999); 31 | if ( !$.Jookie.Get(cookie_name, "idx") ) { 32 | $.Jookie.Set(cookie_name, "idx", 0); 33 | var idx = 0; 34 | } else { 35 | var idx = $.Jookie.Get(cookie_name, "idx"); 36 | } 37 | 38 | if ( $.Jookie.Get(cookie_name, "idx") >= locations_limit ) { 39 | $("#location_save").fadeOut(); 40 | throwError("You may only save " + locations_limit 41 | + " locations - please delete some."); 42 | } else { 43 | // Find the next free index we can use 44 | var i=1; 45 | while ( $.Jookie.Get(cookie_name, i+"_name") && i<=locations_limit ) { 46 | i++; 47 | } 48 | 49 | // We will use this idx for the next location 50 | $.Jookie.Set(cookie_name, i+"_lat", req_lat); 51 | $.Jookie.Set(cookie_name, i+"_lon", req_lon); 52 | $.Jookie.Set(cookie_name, i+"_alt", req_alt); 53 | $.Jookie.Set(cookie_name, i+"_name", req_name); 54 | 55 | // Increase the index 56 | idx++; 57 | $.Jookie.Set(cookie_name, "idx", idx); 58 | 59 | // Close dialog and let the user know it worked 60 | $("#location_save").hide(); 61 | appendDebug("Successfully saved the location to cookie " + cookie_name); 62 | } 63 | 64 | } 65 | 66 | // For when the user clicks the "Custom" link for Launch Site 67 | // Construct and display a table of their custom saved locations stored 68 | // in a cookie, and display it 69 | function constructCookieLocationsTable(cookie_name) { 70 | var t = ""; 71 | t += ""; 72 | 73 | $.Jookie.Initialise(cookie_name, 99999999); 74 | if ( !$.Jookie.Get(cookie_name, "idx") || $.Jookie.Get(cookie_name, "idx") == 0 ) { 75 | throwError("You haven't saved any locations yet. Please click Save Location to do so."); 76 | return false; 77 | } else { 78 | idx = $.Jookie.Get(cookie_name, "idx"); 79 | t += ""; 80 | var i=1; 81 | var j=0; 82 | while ( j"; 90 | t += ""; 91 | j++; 92 | } 93 | i++; 94 | } 95 | t += "
NameUseDelete
"; 86 | t += "Use"; 87 | t += ""; 88 | t += "Delete"; 89 | t += "
"; 96 | $("#locations_table").html(t); 97 | return true; 98 | } 99 | } 100 | 101 | // Given a cookie name and an location index, fill the launch card fields 102 | // before hiding the Custom locations window and plotting the new launch 103 | // site 104 | function setCookieLatLng(cookie_name, idx) { 105 | var req_lat = $.Jookie.Get(cookie_name, idx+"_lat"); 106 | var req_lon= $.Jookie.Get(cookie_name, idx+"_lon"); 107 | var req_alt = $.Jookie.Get(cookie_name, idx+"_alt"); 108 | $("#lat").val(req_lat); 109 | $("#lon").val(req_lon); 110 | $("#initial_alt").val(req_alt); 111 | // Now hide the window 112 | $("#location_save_local").fadeOut(); 113 | SetSiteOther(); 114 | plotClick(); 115 | } 116 | 117 | // Given a cookie name and a location index, delete the custom location that 118 | // is in the cookie 119 | function deleteCookieLocation(cookie_name, idx) { 120 | // Delete the location in the cookie 121 | $.Jookie.Unset(cookie_name, idx+"_lat"); 122 | $.Jookie.Unset(cookie_name, idx+"_lon"); 123 | $.Jookie.Unset(cookie_name, idx+"_alt"); 124 | $.Jookie.Unset(cookie_name, idx+"_name"); 125 | // Decrease quantity in cookie by one 126 | var qty = $.Jookie.Get(cookie_name, "idx"); 127 | qty -= 1; 128 | $.Jookie.Set(cookie_name, "idx", qty); 129 | // Now hide the window 130 | $("#location_save_local").fadeOut(); 131 | } 132 | 133 | -------------------------------------------------------------------------------- /js/pred/pred-ui.js: -------------------------------------------------------------------------------- 1 | /* 2 | * CUSF Landing Prediction Version 2 3 | * Jon Sowman 2010 4 | * jon@hexoc.com 5 | * http://www.hexoc.com 6 | * 7 | * http://github.com/jonsowman/cusf-standalone-predictor 8 | * 9 | * This file contains javascript functions related to the handling 10 | * of the user interface for the predictor. 11 | * 12 | */ 13 | 14 | // Initialise the UI - this must be called on document ready 15 | function initUI() { 16 | // Make UI elements such as windows draggable 17 | $("#input_form").draggable({containment: '#map_canvas', handle: 18 | 'img.handle', snap: '#map_canvas'}); 19 | $("#scenario_info").draggable({containment: '#map_canvas', handle: 20 | 'img.handle', snap: '#map_canvas'}); 21 | $("#location_save").draggable({containment: '#map_canvas', handle: 22 | 'img.handle', snap: '#map_canvas'}); 23 | $("#location_save_local").draggable({containment: '#map_canvas', handle: 24 | 'img.handle', snap: '#map_canvas'}); 25 | $("#burst-calc-wrapper").draggable({containment: '#map_canvas', handle: 26 | 'img.handle', snap: '#map_canvas'}); 27 | 28 | // Activate buttons to jqueryui styling 29 | $("#run_pred_btn").button(); 30 | $("#req_sub_btn").button(); 31 | $("#burst-calc-use").button(); 32 | $("#burst-calc-close").button(); 33 | $("#burst-calc-advanced-show").button(); 34 | $("#burst-calc-advanced-hide").button(); 35 | } 36 | 37 | // Throw an error window containing and a 'close' link 38 | function throwError(data) { 39 | $("#error_message").html(data); 40 | $("#error_window").fadeIn(); 41 | } 42 | 43 | // Reset the GUI to a onLoad state ready for a new prediction to be shown 44 | function resetGUI() { 45 | if (showStatusEventHandle) { 46 | clearTimeout(showStatusEventHandle); 47 | showStatusEventHandle = null; 48 | } 49 | if (firstJSONProgressHandle) { 50 | clearTimeout(firstJSONProgressHandle); 51 | firstJSONProgressHandle = null; 52 | } 53 | 54 | $("#status_message").fadeOut(500); 55 | $("#error_window").fadeOut(500); 56 | $("#modelForm").find("input").attr("disabled", false); 57 | 58 | // now clear the status window 59 | $("#prediction_status").html(""); 60 | cursorPredHide(); 61 | 62 | // bring the input form back up 63 | toggleWindow("input_form", null, null, null, "show"); 64 | toggleWindow("scenario_info", null, null, null, "show"); 65 | // un-fade the map canvas 66 | $("#map_canvas").fadeTo(1500, 1); 67 | } 68 | 69 | // Prevent flicker on fast responses by delaying hide for a small time 70 | function cursorPredHide() { 71 | if (cursorPredHideHandle) 72 | return; 73 | 74 | cursorPredHideHandle = setTimeout(function () { 75 | cursorPredHideHandle = null; 76 | $("#cursor_pred").hide(); 77 | $("#cursor_pred_lastrun").hide(); 78 | $("#cursor_pred_links").hide(); 79 | }, firstAjaxDelay + showStatusDelay); 80 | } 81 | 82 | function cursorPredShow() { 83 | if (cursorPredHideHandle) { 84 | clearTimeout(cursorPredHideHandle); 85 | cursorPredHideHandle = null; 86 | } 87 | $("#cursor_pred").show(); 88 | $("#cursor_pred_lastrun").show(); 89 | $("#cursor_pred_links").show(); 90 | } 91 | 92 | // Append a line to the debug window and scroll the window to the bottom 93 | // Optional boolean second argument will clear the debug window if TRUE 94 | function appendDebug(appendage, clear) { 95 | if ( clear == null ){ 96 | var curr = $("#debuginfo").html(); 97 | curr += "
" + appendage; 98 | $("#debuginfo").html(curr); 99 | } else { 100 | $("#debuginfo").html(""); 101 | } 102 | // keep the debug window scrolled to bottom 103 | scrollToBottom("scenario_template_scroller"); 104 | } 105 | 106 | // A function to scroll a scrollable
all the way to the bottom 107 | function scrollToBottom(div_id) { 108 | $("#"+div_id).stop().animate({scrollTop: $("#"+div_id)[0].scrollHeight}); 109 | } 110 | 111 | // Show or hide GUI windows, can either "toggle", or force hide/show 112 | // Takes the window name, the linker ID, the event handlers for 113 | // 'onhide' and 'onshow', and a boolean 'force' parameter 114 | function toggleWindow(window_name, linker, onhide, onshow, force) { 115 | $("#"+window_name+"").stop(true, true) 116 | 117 | if ( force == null ) { 118 | if( $("#"+window_name).css('display') != "none" ){ 119 | $("#"+window_name+"").hide("slide", { direction: "down" }, 500); 120 | $("#"+linker).html(onhide); 121 | } else { 122 | $("#"+window_name).show("slide", { direction: "down" }, 500); 123 | $("#"+linker).html(onshow); 124 | } 125 | } else if ( force == "hide" ) { 126 | if( $("#"+window_name).css('display') != "none" ){ 127 | $("#"+window_name+"").hide("slide", { direction: "down" }, 500); 128 | $("#"+linker).html(onhide); 129 | } 130 | } else if ( force == "show") { 131 | if( $("#"+window_name).css('display') == "none" ){ 132 | $("#"+window_name).show("slide", { direction: "down" }, 500); 133 | $("#"+linker).html(onshow); 134 | } 135 | } else { 136 | appendDebug("toggleWindow force parameter unrecognised"); 137 | } 138 | } 139 | 140 | // Set the selected item to "Other" in the launch locations selector 141 | function SetSiteOther() { 142 | $("#site").val("Other"); 143 | } 144 | 145 | -------------------------------------------------------------------------------- /css/predictor.css: -------------------------------------------------------------------------------- 1 | /* 2 | * CUSF Landing Prediction Version 2 3 | * http://www.cuspaceflight.co.uk 4 | * 5 | * Jon Sowman 2010 6 | * jon@hexoc.com 7 | * http://www.hexoc.com 8 | * 9 | * http://github.com/jonsowman/cusf-standalone-predictor 10 | * 11 | */ 12 | 13 | /* Basic font-size and family. */ 14 | html { 15 | font-family: sans-serif; 16 | font-size: 12px; 17 | } 18 | 19 | body { 20 | margin: 0; padding: 0; 21 | background-color: #000000; 22 | } 23 | 24 | a { text-decoration: underline; color: #333; cursor: pointer; } 25 | 26 | /* The whoppping map in the centre */ 27 | html, body, #map_canvas { 28 | height: 100%; 29 | width: 100vw; 30 | z-index: 1; 31 | } 32 | 33 | .box{ z-index: 1000 !important ;} 34 | 35 | #status_message{ 36 | position: absolute; 37 | left: 50%; 38 | top: 50%; 39 | width: 400px; 40 | height: 60px; 41 | margin-left: -200px; 42 | margin-top: -50px; 43 | padding: 1em; 44 | text-align: center; 45 | display: none; 46 | } 47 | 48 | #error_window { 49 | position: absolute; 50 | left: 50%; 51 | top: 50%; 52 | width: 400px; 53 | margin-left: -200px; 54 | margin-top: -50px; 55 | padding: 1em; 56 | text-align: center; 57 | display: none; 58 | } 59 | 60 | #debuginfo { 61 | white-space: pre; 62 | } 63 | 64 | #burst-calc-wrapper { 65 | position: absolute; 66 | left: 50%; 67 | top: 50%; 68 | width: 400px; 69 | margin-left: -200px; 70 | margin-top: -200px; 71 | padding: 1em; 72 | text-align: center; 73 | display: none; 74 | } 75 | 76 | #notam-settings-wrapper { 77 | position: absolute; 78 | left: 50%; 79 | top: 50%; 80 | width: 400px; 81 | margin-left: -200px; 82 | margin-top: -200px; 83 | padding: 1em; 84 | text-align: center; 85 | display: none; 86 | } 87 | 88 | /* 89 | #burst-calc { 90 | display: none; 91 | padding: 5px; 92 | text-align: center; 93 | } 94 | */ 95 | 96 | #burst-calc-constants { 97 | display: none; 98 | } 99 | 100 | #location_save { 101 | position: absolute; 102 | left: 50%; 103 | top: 50%; 104 | width: 300px; 105 | margin-left: -150px; 106 | margin-top: -120px; 107 | padding: 1em; 108 | text-align: center; 109 | display: none; 110 | } 111 | 112 | #location_save_local { 113 | position: absolute; 114 | left: 50%; 115 | top: 50%; 116 | width: 300px; 117 | margin-left: -150px; 118 | margin-top: -120px; 119 | padding: 1em; 120 | text-align: center; 121 | display: none; 122 | } 123 | 124 | .box { 125 | position: absolute; 126 | background-color: white; 127 | border: 1px solid #0070a3; 128 | padding: 0.5em; 129 | } 130 | 131 | .box { 132 | background-attachment:initial; 133 | background-clip:initial; 134 | background-color:rgb(255, 255, 255); 135 | background-color:rgba(255, 255, 255, 0.746094); 136 | background-image:initial; 137 | background-origin:initial; 138 | background-position:initial initial; 139 | background-repeat:initial initial; 140 | } 141 | 142 | .box * { 143 | font-size: 12px; 144 | } 145 | 146 | .box table { 147 | margin: 0.25em auto; 148 | white-space: nowrap; 149 | } 150 | 151 | .box h1 { 152 | text-align: center; 153 | margin: 0em 0em 0.5em 0em; 154 | } 155 | 156 | .box p { 157 | margin: 0 auto; 158 | margin-bottom: 0.25em; 159 | width: 25em; 160 | } 161 | 162 | #trail_table { 163 | right: 0; bottom: 0; 164 | border-right: none; 165 | border-bottom: none; 166 | display: none; 167 | } 168 | 169 | #scenario_template { 170 | width: 400px; 171 | left: 0; bottom: 0; 172 | border-left: none; 173 | border-bottom: none; 174 | display: none; 175 | } 176 | 177 | #scenario_template_scroller { 178 | max-height: 400px; 179 | overflow: scroll; 180 | } 181 | 182 | #input_form { 183 | overflow: hidden; 184 | width: 400px; 185 | padding: 5px 5px 0px 5px; 186 | bottom: 0; right: 0; 187 | max-height: 500px; 188 | } 189 | 190 | #launch-card { 191 | width: 100%; 192 | padding: 5px; 193 | } 194 | 195 | #launch-card tr { 196 | width: 50%; 197 | } 198 | 199 | #scenario_info { 200 | position: absolute; 201 | top: 25px; 202 | right: 0px; 203 | width: 400px; 204 | text-align:center; 205 | padding: 5px; 206 | line-height: 130%; 207 | } 208 | 209 | /* about window is a jqueryui dialog */ 210 | #about_window { 211 | display: none; 212 | } 213 | 214 | /* the vehicle_button buttons from spacenear */ 215 | .control_buttons { 216 | border: solid #aed0ea 1px; 217 | background-color: #deedf7; 218 | padding: 3px; 219 | margin-top: 1em; 220 | color: #888; 221 | -webkit-user-select:none; 222 | -moz-user-select:none; 223 | cursor: default; 224 | line-height: 220%; 225 | font-size: 10px; 226 | } 227 | 228 | .control_button { 229 | font-weight: bold; 230 | color: #5c9dc2; 231 | cursor: pointer; 232 | text-decoration: none; 233 | } 234 | 235 | a.control_button:hover { 236 | color: #0070a3; 237 | } 238 | 239 | img.handle { 240 | position: relative; 241 | float:left; 242 | top:0px; 243 | left:0px; 244 | z-index:100; 245 | } 246 | 247 | td.right-td { 248 | text-align: right; 249 | } 250 | 251 | td.spacer { 252 | width: 1em; 253 | } 254 | 255 | table#trails { 256 | border-collapse: collapse; 257 | } 258 | 259 | table#trails td, table#trails th { 260 | border: 1px solid #6666ff; 261 | padding: 0.1em 0.5em; 262 | text-align: center; 263 | } 264 | 265 | table#trails th { 266 | background-color: #6666ff; 267 | color: white; 268 | } 269 | 270 | 271 | .tipsy { padding: 5px; font-size: 10px; opacity: 0.8; filter: alpha(opacity=80); background-repeat: no-repeat; background-image: url(../images/tipsy.gif); } 272 | .tipsy-inner { padding: 5px 8px 4px 8px; background-color: black; color: white; max-width: 200px; text-align: center; } 273 | .tipsy-inner { -moz-border-radius:3px; -webkit-border-radius:3px; } 274 | .tipsy-north { background-position: top center; } 275 | .tipsy-south { background-position: bottom center; } 276 | .tipsy-east { background-position: right center; } 277 | .tipsy-west { background-position: left center; } 278 | -------------------------------------------------------------------------------- /js/pred/pred-event.js: -------------------------------------------------------------------------------- 1 | /* 2 | * CUSF Landing Prediction Version 2 3 | * Jon Sowman 2010 4 | * jon@hexoc.com 5 | * http://www.hexoc.com 6 | * 7 | * http://github.com/jonsowman/cusf-standalone-predictor 8 | * 9 | * This file contains the event handlers used in the predictor, which are 10 | * numerous. They are divided into functions that setup handlers for each 11 | * part of the predictor, and a calling function 12 | * 13 | */ 14 | 15 | function setupEventHandlers() { 16 | EH_LaunchCard(); 17 | EH_BurstCalc(); 18 | EH_NOTAMSettings(); 19 | EH_ScenarioInfo(); 20 | EH_LocationSave(); 21 | 22 | // Tipsylink tooltip class activation 23 | $(".tipsyLink").tipsy({fade: true}); 24 | 25 | // Add the onmove event handler to the map canvas 26 | map.on('mousemove', function(event) { 27 | showMousePos(event.latlng.wrap()); 28 | }); 29 | } 30 | 31 | function EH_BurstCalc() { 32 | // Activate the "use burst calc" links 33 | $("#burst-calc-show").click(function() { 34 | $("#burst-calc-wrapper").show(); 35 | }); 36 | $("#burst-calc-show").hover( 37 | function() { 38 | $("#ascent,#burst").css("background-color", "#AACCFF"); 39 | }, 40 | function() { 41 | $("#ascent,#burst").css("background-color", ""); 42 | }); 43 | $("#burst-calc-use").click(function() { 44 | // Write the ascent rate and burst altitude to the launch card 45 | $("#ascent").val($("#ar").html()); 46 | $("#burst").val($("#ba").html()); 47 | $("#burst-calc-wrapper").hide(); 48 | }); 49 | $("#burst-calc-close").click(function() { 50 | // Close the burst calc without doing anything 51 | $("#burst-calc-wrapper").hide(); 52 | $("#modelForm").show(); 53 | }); 54 | $("#burst-calc-advanced-show").click(function() { 55 | // Show the burst calculator constants 56 | // We use a callback function to fade in the new content to make 57 | // sure the old content has gone, in order to create a smooth effect 58 | $("#burst-calc").fadeOut('fast', function() { 59 | $("#burst-calc-constants").fadeIn(); 60 | }); 61 | }); 62 | $("#burst-calc-advanced-hide").click(function() { 63 | // Show the burst calculator constants 64 | $("#burst-calc-constants").fadeOut('fast', function() { 65 | $("#burst-calc").fadeIn(); 66 | }); 67 | }); 68 | } 69 | 70 | function EH_NOTAMSettings() { 71 | // Activate the checkbox 72 | $("#notam-display").click(function() { 73 | if (document.modelForm.notams.checked){ 74 | if (kmlLayer == null) kmlLayer = new google.maps.KmlLayer('http://www.habhub.org/kml_testing/notam_and_restrict.kml', {preserveViewport: true}); 75 | kmlLayer.setMap(map); 76 | } 77 | else { 78 | kmlLayer.setMap(null); 79 | } 80 | }); 81 | // Activate the "notam settings" links 82 | $("#notam-settings-show").click(function() { 83 | $("#notam-settings-wrapper").show(); 84 | }); 85 | $("#notam-settings-close").click(function() { 86 | // Close the notam settings doing anything 87 | $("#notam-settings-wrapper").hide(); 88 | $("#modelForm").show(); 89 | }); 90 | } 91 | 92 | function EH_LaunchCard() { 93 | // Activate the "Set with Map" link 94 | $("#setWithClick").click(function() { 95 | setLatLonByClick(true); 96 | }); 97 | $("#setWithClick,#req_open").hover( 98 | function() { 99 | $("#lat,#lon").css("background-color", "#AACCFF"); 100 | }, 101 | function() { 102 | $("#lat,#lon").css("background-color", ""); 103 | }); 104 | // Launch card parameter onchange event handlers 105 | $("#lat").change(function() { 106 | plotClick(); 107 | }); 108 | $("#lon").change(function() { 109 | plotClick(); 110 | }); 111 | 112 | $("#site").change(function() { 113 | changeLaunchSite(); 114 | }); 115 | } 116 | 117 | function EH_ScenarioInfo() { 118 | // Controls in the Scenario Information window 119 | $("#showHideDebug").click(function() { 120 | toggleWindow("scenario_template", "showHideDebug", "Show Debug", "Hide Debug"); 121 | }); 122 | $("#showHideDebug_status").click(function() { 123 | toggleWindow("scenario_template", "showHideDebug", "Show Debug", "Hide Debug"); 124 | }); 125 | $("#showHideForm").click(function() { 126 | toggleWindow("input_form", "showHideForm", "Show Launch Card", 127 | "Hide Launch Card"); 128 | }); 129 | $("#closeErrorWindow").click(function() { 130 | $("#error_window").fadeOut(); 131 | }); 132 | 133 | $("#about_window_show").click(function() { 134 | $("#about_window").dialog({ 135 | modal:true, 136 | width:600, 137 | height: $(document).height() - 200, 138 | buttons: { 139 | Close: function() { 140 | $(this).dialog('close'); 141 | } 142 | } 143 | }); 144 | }); 145 | } 146 | 147 | function EH_LocationSave() { 148 | // Location saving to cookies event handlers 149 | $("#req_sub_btn").click(function() { 150 | saveLocationToCookie(); 151 | }); 152 | $("#cookieLocations").click(function() { 153 | appendDebug("User requested locally saved launch sites"); 154 | if ( constructCookieLocationsTable("cusf_predictor") ) { 155 | $("#location_save_local").fadeIn(); 156 | } 157 | }); 158 | $("#req_open").click(function() { 159 | var lat = $("#lat").val(); 160 | var lon = $("#lon").val(); 161 | $("#req_lat").val(lat); 162 | $("#req_lon").val(lon); 163 | $("#req_alt").val($("#initial_alt").val()); 164 | appendDebug("Trying to reverse geo-code the launch point"); 165 | // No Leaflet geocode equivalent, so commenting this out for now. 166 | //rvGeocode(lat, lon, "req_name"); 167 | $("#location_save").fadeIn(); 168 | }) 169 | $("#req_close").click(function() { 170 | $("#location_save").fadeOut(); 171 | }); 172 | $("#locations_close").click(function() { 173 | $("#location_save_local").fadeOut(); 174 | }); 175 | } 176 | -------------------------------------------------------------------------------- /js/pred/pred-map.js: -------------------------------------------------------------------------------- 1 | /* 2 | * CUSF Landing Prediction Version 2 3 | * Jon Sowman 2010 4 | * jon@hexoc.com 5 | * http://www.hexoc.com 6 | * 7 | * http://github.com/jonsowman/cusf-standalone-predictor 8 | * 9 | * This file contains all of the prediction javascript functions 10 | * that are explicitly related to Google Map manipulation 11 | * 12 | */ 13 | 14 | // Initialise the map canvas with (lat, long, zoom) 15 | function initMap(centre_lat, centre_lon, zoom_level) { 16 | // 17 | // LEAFLET MAP SETUP 18 | // 19 | // Setup a basic Leaflet map 20 | map = L.map('map_canvas').setView([centre_lat, centre_lon], zoom_level); 21 | 22 | // Add OSM Map Layer 23 | var osm_map = L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', { 24 | attribution: '© OpenStreetMap contributors' 25 | }).addTo(map); 26 | 27 | // Open Topo 28 | var osm_topo_map = L.tileLayer('https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png', { 29 | attribution: '© OpenTopoMap contributors' 30 | }); 31 | 32 | // Add ESRI Satellite Map layers. 33 | var esrimapLink = 34 | 'Esri'; 35 | var esriwholink = 36 | 'i-cubed, USDA, USGS, AEX, GeoEye, Getmapping, Aerogrid, IGN, IGP, UPR-EGP, and the GIS User Community'; 37 | var esri_sat_map = L.tileLayer( 38 | 'http://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', 39 | { 40 | attribution: '© '+esrimapLink+', '+esriwholink, 41 | maxZoom: 18, 42 | }); 43 | 44 | var map_layers = {'OSM':osm_map, 'ESRI Satellite':esri_sat_map, 'OpenTopoMap':osm_topo_map}; 45 | 46 | map.addControl(new L.Control.Layers(map_layers, null, {position: 'topleft'})); 47 | 48 | // Map scale 49 | L.control.scale({imperial: false, metric: true}).addTo(map); 50 | 51 | 52 | } 53 | 54 | // Enable or disable user control of the map canvas, including scrolling, 55 | // zooming and clicking 56 | function enableMap(map, state) { 57 | if ( state != false && state != true) { 58 | appendDebug("Unrecognised map state"); 59 | } else if (state == false) { 60 | map.draggable = false; 61 | map.disableDoubleClickZoom = true; 62 | map.scrollwheel = false; 63 | map.navigationControl = false; 64 | } else if (state == true ) { 65 | map.draggable = true; 66 | map.disableDoubleClickZoom = false; 67 | map.scrollwheel = false; 68 | map.navigationControl = true; 69 | } 70 | } 71 | 72 | // This should be called on a "mousemove" event handler on the map canvas 73 | // and will update scenario information display 74 | function showMousePos(LatLng) { 75 | var curr_lat = LatLng.lat.toFixed(4); 76 | var curr_lon = LatLng.lng.toFixed(4); 77 | $("#cursor_lat").html(curr_lat); 78 | $("#cursor_lon").html(curr_lon); 79 | // if we have a prediction displayed 80 | // show range from launch and land: 81 | if ( (map_items['launch_marker'] != null) && (hourly_mode == false)) { 82 | var launch_pt = map_items['launch_marker'].getLatLng(); 83 | var land_pt = map_items['land_marker'].getLatLng(); 84 | var range_launch = distHaversine(launch_pt, LatLng, 1); 85 | var range_land = distHaversine(land_pt, LatLng, 1); 86 | $("#cursor_pred_launchrange").html(range_launch); 87 | $("#cursor_pred_landrange").html(range_land); 88 | } 89 | 90 | } 91 | 92 | // Read the latitude and longitude currently in the launch card and plot 93 | // a marker there with hover information 94 | function plotClick() { 95 | // Clear the old marker 96 | clearMapItems(); 97 | // Get the new values from the form 98 | click_lat = parseFloat($("#lat").val()); 99 | click_lon = parseFloat($("#lon").val()); 100 | // Make sure the data is valid before we try and do anything with it 101 | if ( isNaN(click_lat) || isNaN(click_lon) ) return; 102 | var click_pt = new L.LatLng(click_lat, click_lon); 103 | 104 | // var launch_icon = new google.maps.MarkerImage(launch_img, 105 | // new google.maps.Size(10, 10), 106 | // new google.maps.Point(0, 0), 107 | // new google.maps.Point(5, 5) 108 | // ); 109 | 110 | launch_icon = L.icon({ 111 | iconUrl: launch_img, 112 | iconSize: [10,10], 113 | iconAnchor: [5,5] 114 | }); 115 | 116 | clickIconTitle = 'Currently selected launch location (' + click_lat + ', ' + click_lon+')' 117 | 118 | clickMarker = L.marker(click_pt, 119 | { 120 | title:clickIconTitle, 121 | icon: launch_icon 122 | }) 123 | .bindTooltip(clickIconTitle,{permanent:false,direction:'right'}) 124 | .addTo(map); 125 | 126 | map_items['clickMarker'] = clickMarker; 127 | map.setView(click_pt, 8); 128 | } 129 | 130 | // Given a GLatLng object, write the latitude and longitude to the launch card 131 | function setFormLatLon(LatLng) { 132 | appendDebug("Trying to set the form lat long"); 133 | $("#lat").val(LatLng.lat.toFixed(4)); 134 | $("#lon").val(LatLng.lng.toFixed(4)); 135 | // Remove the event handler so another click doesn't register 136 | setLatLonByClick(false); 137 | // Change the dropdown to read "other" 138 | SetSiteOther(); 139 | // Plot the new marker for launch location 140 | appendDebug("Plotting the new launch location marker"); 141 | plotClick(); 142 | } 143 | 144 | // Enable or disable an event handler which, when a mouse click is detected 145 | // on the map canvas, will write the coordinates of the clicked place to the 146 | // launch card 147 | function setLatLonByClick(state) { 148 | if ( state == true ) { 149 | // Check this listener doesn't already exist 150 | if (!clickListener) { 151 | appendDebug("Enabling the set with click listener"); 152 | clickListener = map.on('click', function(event) { 153 | appendDebug("Got a click from user, setting values into form"); 154 | $("#error_window").fadeOut(); 155 | setFormLatLon(event.latlng.wrap()); 156 | }); 157 | } 158 | // Tell the user what to do next 159 | throwError("Now click your desired launch location on the map"); 160 | } else if ( state == false ) { 161 | appendDebug("Removing the set with click listener"); 162 | map.off('click',clickListener); 163 | clickListener = null; 164 | } else { 165 | appendDebug("Unrecognised state for setLatLonByClick"); 166 | } 167 | } 168 | 169 | // An associative array exists globally containing all objects we have placed 170 | // onto the map canvas - this function clears all of them 171 | function clearMapItems() { 172 | cursorPredHide(); 173 | if( getAssocSize(map_items) > 0 ) { 174 | appendDebug("Clearing previous map trace"); 175 | for( i in map_items ) { 176 | map_items[i].remove(); 177 | } 178 | } 179 | map_items = []; 180 | 181 | // Clear hourly prediction data too 182 | if( getAssocSize(hourly_predictions) > 0 ) { 183 | appendDebug("Clearing hourly prediction data."); 184 | for( i in hourly_predictions) { 185 | for (j in hourly_predictions[i]['layers']){ 186 | hourly_predictions[i]['layers'][j].remove(); 187 | } 188 | } 189 | } 190 | 191 | if(hourly_polyline){ 192 | hourly_polyline.remove(); 193 | hourly_polyline = null; 194 | } 195 | } 196 | 197 | // The Haversine formula to calculate the distance across the surface between 198 | // two points on the Earth 199 | distHaversine = function(p1, p2, precision) { 200 | var R = 6371; // earth's mean radius in km 201 | var dLat = rad(p2.lat - p1.lat); 202 | var dLong = rad(p2.lng - p1.lng); 203 | 204 | var a = Math.sin(dLat/2) * Math.sin(dLat/2) + 205 | Math.cos(rad(p1.lat)) * Math.cos(rad(p2.lat)) * Math.sin(dLong/2) * Math.sin(dLong/2); 206 | var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a)); 207 | var d = R * c; 208 | if ( precision == null ) { 209 | return d.toFixed(3); 210 | } else { 211 | return d.toFixed(precision); 212 | } 213 | } 214 | 215 | 216 | // MJ: Commented out until we can find an equivalent geocoding API. 217 | // Given a latitude, longitude, and a field to write the result to, 218 | // find the name of the place using Google "reverse Geocode" API feature 219 | // function rvGeocode(lat, lon, fillField) { 220 | // var geocoder = new google.maps.Geocoder(); 221 | // var latlng = new google.maps.LatLng(parseFloat(lat), parseFloat(lon)); 222 | // var coded = "Unnamed"; 223 | // geocoder.geocode({'latLng': latlng}, function(results, status) { 224 | // if ( status == google.maps.GeocoderStatus.OK ) { 225 | // // Successfully got rv-geocode information 226 | // appendDebug("Got a good response from the geocode server"); 227 | // coded = results[1].address_components[1].short_name; 228 | // } else { 229 | // appendDebug("The rv-geocode failed: " + status); 230 | // } 231 | // // Now write the value to the field 232 | // $("#"+fillField+"").val(coded); 233 | // }); 234 | // } 235 | -------------------------------------------------------------------------------- /js/calc/calc.js: -------------------------------------------------------------------------------- 1 | function get_value(id) { 2 | return parseFloat(document.getElementById(id).value); 3 | } 4 | 5 | function clear_errors() { 6 | var ids = ['mp', 'tar', 'tba', 'rho_g', 'rho_a', 'adm', 'bd', 'cd', 7 | 'bd_c', 'cd_c']; 8 | 9 | for(var i in ids) { 10 | document.getElementById(ids[i]).style.backgroundColor = ''; 11 | } 12 | 13 | var ids = ['mp_w', 'mb_w', 'tar_w', 'tba_w']; 14 | for(i in ids) { 15 | document.getElementById(ids[i]).innerHTML = ' '; 16 | } 17 | } 18 | 19 | function show_error(id) { 20 | document.getElementById(id).style.backgroundColor = '#f99'; 21 | } 22 | 23 | function set_error(id, error) { 24 | show_error(id); 25 | document.getElementById(id+"_w").innerHTML = error; 26 | } 27 | 28 | function sanity_check_inputs(mb, mp, mp_set, tar, tba, tar_set, tba_set) { 29 | if(tar_set && tba_set) { 30 | set_error('tar', "Can't specify both!"); 31 | set_error('tba', "Can't specify both!"); 32 | return 1; 33 | } else if(!tar_set && !tba_set) { 34 | set_error('tar', "Must specify at least one!"); 35 | set_error('tba', "Must specify at least one!"); 36 | return 1; 37 | } 38 | 39 | if(tar_set && tar < 0) { 40 | set_error('tar', "Can't be negative!"); 41 | return 1; 42 | } else if(tar_set && tar > 10) { 43 | set_error('tar', "Too large! (> 10m/s)"); 44 | return 1; 45 | } 46 | 47 | if(tba_set && tba < 10000) { 48 | set_error('tba', "Too low! (< 10km)"); 49 | return 1; 50 | } else if(tba_set && tba > 40000) { 51 | set_error('tba', "Too high! (> 40km)"); 52 | return 1; 53 | } 54 | 55 | if(!mp_set) { 56 | set_error('mp', "Mass required!"); 57 | return 1; 58 | } else if(mp < 20) { 59 | set_error('mp', "Too small! (< 20g)"); 60 | return 1; 61 | } else if(mp > 20000) { 62 | set_error('mp', "Too large! (> 20kg)"); 63 | return 1; 64 | } 65 | 66 | return 0; 67 | 68 | } 69 | 70 | function sanity_check_constants(rho_g, rho_a, adm, ga, bd, cd) { 71 | if(!rho_a || rho_a < 0) { 72 | show_error('rho_a'); 73 | return 1; 74 | } 75 | if(!rho_g || rho_g < 0 || rho_g > rho_a) { 76 | show_error('rho_g'); 77 | return 1; 78 | } 79 | if(!adm || adm < 0) { 80 | show_error('adm'); 81 | return 1; 82 | } 83 | if(!ga || ga < 0) { 84 | show_error('ga'); 85 | return 1; 86 | } 87 | if(!cd || cd < 0 || cd > 1) { 88 | show_error('cd'); 89 | return 1; 90 | } 91 | if(!bd || bd < 0) { 92 | show_error('bd'); 93 | return 1; 94 | } 95 | 96 | return 0; 97 | } 98 | 99 | function find_rho_g() { 100 | var gas = document.getElementById('gas').value; 101 | var rho_g; 102 | 103 | switch(gas) { 104 | case 'he': 105 | rho_g = 0.1786; 106 | document.getElementById('rho_g').value = rho_g; 107 | document.getElementById('rho_g').disabled = "disabled"; 108 | break; 109 | case 'h': 110 | rho_g = 0.0899; 111 | document.getElementById('rho_g').value = rho_g; 112 | document.getElementById('rho_g').disabled = "disabled"; 113 | break; 114 | case 'ch4': 115 | rho_g = 0.6672; 116 | document.getElementById('rho_g').value = rho_g; 117 | document.getElementById('rho_g').disabled = "disabled"; 118 | break; 119 | default: 120 | document.getElementById('rho_g').disabled = ""; 121 | rho_g = get_value('rho_g'); 122 | break; 123 | } 124 | 125 | return rho_g; 126 | } 127 | 128 | function find_bd(mb) { 129 | var bds = new Array(); 130 | 131 | // From Kaymont Totex Sounding Balloon Data 132 | bds["k50"] = 0.88; 133 | bds["k100"] = 1.96; 134 | bds["k150"] = 2.52; 135 | bds["k200"] = 3.00; 136 | bds["k300"] = 3.78; 137 | bds["k350"] = 4.12; 138 | //bds["k450"] = 4.72; // Discontinued? 139 | //bds["k500"] = 4.99; // Discontinued? 140 | bds["k600"] = 6.02; 141 | //bds["k700"] = 6.53; // Discontinued? 142 | bds["k800"] = 7.00; 143 | bds["k1000"] = 7.86; 144 | bds["k1200"] = 8.63; 145 | bds["k1500"] = 9.44; 146 | bds["k1600"] = 9.71; 147 | bds["k1800"] = 9.98; 148 | bds["k2000"] = 10.54; 149 | bds["k3000"] = 13.00; 150 | bds["k4000"] = 15.06; 151 | // Updated 2024-11 from https://www.hwoyee.com/weather-balloon-meteorological-balloon-for-weather-sounding-wind-or-cloud-detection-near-space-researchesgiant-round-balloons-huge-balloons-product/ 152 | bds["h100"] = 2.00; 153 | bds["h200"] = 2.97; 154 | bds["h300"] = 4.30; 155 | bds["h350"] = 4.80; 156 | bds["h500"] = 5.80; 157 | bds["h600"] = 6.50; 158 | bds["h750"] = 6.90; 159 | bds["h800"] = 7.00; 160 | bds["h1000"] = 8.00; 161 | bds["h1200"] = 9.10; 162 | bds["h1600"] = 10.00; 163 | // These two are fudged a little 164 | bds["h2000"] = 11.00; 165 | bds["h3000"] = 12.00; 166 | // PAWAN data from 167 | // https://sites.google.com/site/balloonnewswebstore/1200g-balloon-data 168 | bds["p1200"] = 8.0; 169 | 170 | 171 | var bd_c = document.getElementById('bd_c').checked; 172 | var bd; 173 | 174 | if(bd_c) { 175 | bd = get_value('bd'); 176 | document.getElementById('bd').disabled = ""; 177 | } else { 178 | bd = bds[mb]; 179 | document.getElementById('bd').disabled = "disabled"; 180 | document.getElementById('bd').value = bd; 181 | } 182 | 183 | return bd; 184 | } 185 | 186 | function find_cd(mb) { 187 | var cds = new Array(); 188 | 189 | // From Kaymont Totex Sounding Balloon Data 190 | cds["k50"] = 0.25; 191 | cds["k100"] = 0.25; 192 | cds["k150"] = 0.25; 193 | cds["k200"] = 0.25; 194 | cds["k300"] = 0.25; 195 | cds["k350"] = 0.25; 196 | cds["k450"] = 0.25; 197 | cds["k500"] = 0.25; 198 | cds["k600"] = 0.30; 199 | cds["k700"] = 0.30; 200 | cds["k800"] = 0.30; 201 | cds["k1000"] = 0.30; 202 | cds["k1200"] = 0.25; 203 | cds["k1500"] = 0.25; 204 | cds["k1600"] = 0.25; 205 | cds["k1800"] = 0.25; 206 | cds["k2000"] = 0.25; 207 | cds["k3000"] = 0.25; 208 | cds["k4000"] = 0.25; 209 | // Hwoyee data just guesswork 210 | cds["h200"] = 0.25; 211 | cds["h300"] = 0.25; 212 | cds["h350"] = 0.25; 213 | cds["h400"] = 0.25; 214 | cds["h500"] = 0.25; 215 | cds["h600"] = 0.30; 216 | cds["h750"] = 0.30; 217 | cds["h800"] = 0.30; 218 | cds["h950"] = 0.30; 219 | cds["h1000"] = 0.30; 220 | cds["h1200"] = 0.25; 221 | cds["h1500"] = 0.25; 222 | cds["h1600"] = 0.25; 223 | cds["h2000"] = 0.25; 224 | cds["h3000"] = 0.25; 225 | // PAWAN data also guesswork 226 | cds["p1200"] = 0.25; 227 | 228 | var cd_c = document.getElementById('cd_c').checked; 229 | var cd; 230 | 231 | if(cd_c) { 232 | cd = get_value('cd'); 233 | document.getElementById('cd').disabled = ""; 234 | } else { 235 | cd = cds[mb]; 236 | document.getElementById('cd').disabled = "disabled"; 237 | document.getElementById('cd').value = cd; 238 | } 239 | 240 | return cd; 241 | } 242 | 243 | function calc_update() { 244 | // Reset error status 245 | clear_errors(); 246 | 247 | // Get input values and check them 248 | var mb = document.getElementById('mb').value; 249 | var mp = get_value('mp'); 250 | var tar = get_value('tar'); 251 | var tba = get_value('tba'); 252 | var mp_set = 0; 253 | var tar_set = 0; 254 | var tba_set = 0; 255 | 256 | if(document.getElementById('mp').value) 257 | mp_set = 1; 258 | if(document.getElementById('tar').value) 259 | tar_set = 1; 260 | if(document.getElementById('tba').value) 261 | tba_set = 1; 262 | 263 | if(sanity_check_inputs(mb, mp, mp_set, tar, tba, tar_set, tba_set)) 264 | return; 265 | 266 | // Get constants and check them 267 | var rho_g = find_rho_g(); 268 | var rho_a = get_value('rho_a'); 269 | var adm = get_value('adm'); 270 | var ga = get_value('ga'); 271 | var bd = find_bd(mb); 272 | var cd = find_cd(mb); 273 | 274 | if(sanity_check_constants(rho_g, rho_a, adm, ga, bd, cd)) 275 | return; 276 | 277 | // Do some maths 278 | mb = parseFloat(mb.substr(1)) / 1000.0; 279 | mp = mp / 1000.0; 280 | 281 | var ascent_rate = 0; 282 | var burst_altitude = 0; 283 | var time_to_burst = 0; 284 | var neck_lift = 0; 285 | var launch_radius = 0; 286 | var launch_volume = 0; 287 | 288 | var burst_volume = (4.0/3.0) * Math.PI * Math.pow(bd / 2.0, 3); 289 | 290 | if(tba_set) { 291 | launch_volume = burst_volume * Math.exp((-tba) / adm); 292 | launch_radius = Math.pow((3*launch_volume)/(4*Math.PI), (1/3)); 293 | } else if(tar_set) { 294 | var a = ga * (rho_a - rho_g) * (4.0 / 3.0) * Math.PI; 295 | var b = -0.5 * Math.pow(tar, 2) * cd * rho_a * Math.PI; 296 | var c = 0; 297 | var d = - (mp + mb) * ga; 298 | 299 | 300 | var f = (((3*c)/a) - (Math.pow(b, 2) / Math.pow(a,2)) / 3.0); 301 | // 2025-08-19 - Original calculation 302 | // var g = ( 303 | // ((2*Math.pow(b,3))/Math.pow(a,3)) - 304 | // ((9*b*c)/(Math.pow(a,2))) + ((27*d)/a) / 27.0 305 | // ); 306 | // 2025-08-19 - Modified to match sondehub.org/calc 307 | // Just moving a bracket around. 308 | // This way at least we end up with the right target ascent rate and not something higher. 309 | var g = ( 310 | ((2*Math.pow(b,3))/Math.pow(a,3)) - 311 | ((9*b*c)/(Math.pow(a,2))) + ((27*d)/a)) / 27.0 312 | ; 313 | 314 | var h = (Math.pow(g,2) / 4.0) + (Math.pow(f,3) / 27.0); 315 | 316 | if(h>0) { 317 | // One real root. This is what should happen. 318 | var R = (-0.5 * g) + Math.sqrt(h); 319 | var S = Math.pow(R, 1.0/3.0); 320 | var T = (-0.5 * g) - Math.sqrt(h); 321 | var U = Math.pow(T, 1.0/3.0); 322 | launch_radius = (S+U) - (b/(3*a)); 323 | 324 | 325 | } else if(f==0 && g==0 && h==0) { 326 | // Three real and equal roots 327 | // Will this ever even happen? 328 | launch_radius = -1 * Math.pow(d/a, 1.0/3.0); 329 | } else if(h <= 0) { 330 | // Three real and different roots 331 | // What the hell do we do?! 332 | // It needs trig! fffff 333 | var i = Math.sqrt((Math.pow(g,2)/4.0) - h); 334 | var j = Math.pow(i, 1.0/3.0); 335 | var k = Math.acos(-g / (2*i)); 336 | var L = -1 * j; 337 | var M = Math.cos(K/3.0); 338 | var N = Math.sqrt(3) * Math.sin(K/3.0); 339 | var P = (b/(3*a)) * -1; 340 | var r1 = 2*j*Math.cos(k/3.0) - (b/(3*a)); 341 | var r2 = L * (M + N) + P; 342 | var r3 = L * (M - N) + P; 343 | 344 | alert("Three possible solutions found: " 345 | + r1 + ", " + r2 + ", " + r3); 346 | 347 | if(r1 > 0) { 348 | launch_radius = r1; 349 | } else if(r2 > 0) { 350 | launch_radius = r2; 351 | } else if(r3 > 0) { 352 | launch_radius = r3; 353 | } 354 | } else { 355 | // No real roots 356 | } 357 | } 358 | 359 | var launch_area = Math.PI * Math.pow(launch_radius, 2); 360 | var launch_volume = (4.0/3.0) * Math.PI * Math.pow(launch_radius, 3); 361 | var density_difference = rho_a - rho_g; 362 | var gross_lift = launch_volume * density_difference; 363 | neck_lift = (gross_lift - mb) * 1000; 364 | var total_mass = mp + mb; 365 | var free_lift = (gross_lift - total_mass) * ga; 366 | ascent_rate = Math.sqrt(free_lift / (0.5 * cd * launch_area * rho_a)); 367 | var volume_ratio = launch_volume / burst_volume; 368 | burst_altitude = -(adm) * Math.log(volume_ratio); 369 | time_to_burst = (burst_altitude / ascent_rate) / 60.0; 370 | 371 | if(isNaN(ascent_rate)) { 372 | set_error('tba', "Altitude unreachable
for this configuration."); 373 | return; 374 | } 375 | 376 | ascent_rate = ascent_rate.toFixed(2); 377 | burst_altitude = burst_altitude.toFixed(); 378 | time_to_burst = time_to_burst.toFixed(); 379 | neck_lift = neck_lift.toFixed(); 380 | launch_litres = (launch_volume * 1000).toFixed(); 381 | launch_cf = (launch_volume * 35.31).toFixed(1); 382 | launch_volume = launch_volume.toFixed(2); 383 | 384 | document.getElementById('ar').innerHTML = ascent_rate; 385 | document.getElementById('ba').innerHTML = burst_altitude; 386 | document.getElementById('ttb').innerHTML = time_to_burst; 387 | document.getElementById('nl').innerHTML = neck_lift; 388 | document.getElementById('lv_m3').innerHTML = launch_volume; 389 | document.getElementById('lv_l').innerHTML = launch_litres; 390 | document.getElementById('lv_cf').innerHTML = launch_cf; 391 | } 392 | 393 | function calc_init() { 394 | 395 | var ids = ['mb', 'mp', 'tar', 'tba', 'gas', 'rho_g', 'rho_a', 'adm', 'bd', 'cd', 'bd_c', 'cd_c']; 396 | for(var i in ids) { 397 | document.getElementById(ids[i]).onchange = calc_update; 398 | } 399 | calc_update(); 400 | } 401 | -------------------------------------------------------------------------------- /css/leaflet.css: -------------------------------------------------------------------------------- 1 | /* required styles */ 2 | 3 | .leaflet-pane, 4 | .leaflet-tile, 5 | .leaflet-marker-icon, 6 | .leaflet-marker-shadow, 7 | .leaflet-tile-container, 8 | .leaflet-pane > svg, 9 | .leaflet-pane > canvas, 10 | .leaflet-zoom-box, 11 | .leaflet-image-layer, 12 | .leaflet-layer { 13 | position: absolute; 14 | left: 0; 15 | top: 0; 16 | } 17 | .leaflet-container { 18 | overflow: hidden; 19 | } 20 | .leaflet-tile, 21 | .leaflet-marker-icon, 22 | .leaflet-marker-shadow { 23 | -webkit-user-select: none; 24 | -moz-user-select: none; 25 | user-select: none; 26 | -webkit-user-drag: none; 27 | } 28 | /* Safari renders non-retina tile on retina better with this, but Chrome is worse */ 29 | .leaflet-safari .leaflet-tile { 30 | image-rendering: -webkit-optimize-contrast; 31 | } 32 | /* hack that prevents hw layers "stretching" when loading new tiles */ 33 | .leaflet-safari .leaflet-tile-container { 34 | width: 1600px; 35 | height: 1600px; 36 | -webkit-transform-origin: 0 0; 37 | } 38 | .leaflet-marker-icon, 39 | .leaflet-marker-shadow { 40 | display: block; 41 | } 42 | /* .leaflet-container svg: reset svg max-width decleration shipped in Joomla! (joomla.org) 3.x */ 43 | /* .leaflet-container img: map is broken in FF if you have max-width: 100% on tiles */ 44 | .leaflet-container .leaflet-overlay-pane svg, 45 | .leaflet-container .leaflet-marker-pane img, 46 | .leaflet-container .leaflet-shadow-pane img, 47 | .leaflet-container .leaflet-tile-pane img, 48 | .leaflet-container img.leaflet-image-layer { 49 | max-width: none !important; 50 | max-height: none !important; 51 | } 52 | 53 | .leaflet-container.leaflet-touch-zoom { 54 | -ms-touch-action: pan-x pan-y; 55 | touch-action: pan-x pan-y; 56 | } 57 | .leaflet-container.leaflet-touch-drag { 58 | -ms-touch-action: pinch-zoom; 59 | /* Fallback for FF which doesn't support pinch-zoom */ 60 | touch-action: none; 61 | touch-action: pinch-zoom; 62 | } 63 | .leaflet-container.leaflet-touch-drag.leaflet-touch-zoom { 64 | -ms-touch-action: none; 65 | touch-action: none; 66 | } 67 | .leaflet-container { 68 | -webkit-tap-highlight-color: transparent; 69 | } 70 | .leaflet-container a { 71 | -webkit-tap-highlight-color: rgba(51, 181, 229, 0.4); 72 | } 73 | .leaflet-tile { 74 | filter: inherit; 75 | visibility: hidden; 76 | } 77 | .leaflet-tile-loaded { 78 | visibility: inherit; 79 | } 80 | .leaflet-zoom-box { 81 | width: 0; 82 | height: 0; 83 | -moz-box-sizing: border-box; 84 | box-sizing: border-box; 85 | z-index: 800; 86 | } 87 | /* workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=888319 */ 88 | .leaflet-overlay-pane svg { 89 | -moz-user-select: none; 90 | } 91 | 92 | .leaflet-pane { z-index: 400; } 93 | 94 | .leaflet-tile-pane { z-index: 200; } 95 | .leaflet-overlay-pane { z-index: 400; } 96 | .leaflet-shadow-pane { z-index: 500; } 97 | .leaflet-marker-pane { z-index: 600; } 98 | .leaflet-tooltip-pane { z-index: 650; } 99 | .leaflet-popup-pane { z-index: 700; } 100 | 101 | .leaflet-map-pane canvas { z-index: 100; } 102 | .leaflet-map-pane svg { z-index: 200; } 103 | 104 | .leaflet-vml-shape { 105 | width: 1px; 106 | height: 1px; 107 | } 108 | .lvml { 109 | behavior: url(#default#VML); 110 | display: inline-block; 111 | position: absolute; 112 | } 113 | 114 | 115 | /* control positioning */ 116 | 117 | .leaflet-control { 118 | position: relative; 119 | z-index: 800; 120 | pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */ 121 | pointer-events: auto; 122 | } 123 | .leaflet-top, 124 | .leaflet-bottom { 125 | position: absolute; 126 | z-index: 1000; 127 | pointer-events: none; 128 | } 129 | .leaflet-top { 130 | top: 0; 131 | } 132 | .leaflet-right { 133 | right: 0; 134 | } 135 | .leaflet-bottom { 136 | bottom: 0; 137 | } 138 | .leaflet-left { 139 | left: 0; 140 | } 141 | .leaflet-control { 142 | float: left; 143 | clear: both; 144 | } 145 | .leaflet-right .leaflet-control { 146 | float: right; 147 | } 148 | .leaflet-top .leaflet-control { 149 | margin-top: 10px; 150 | } 151 | .leaflet-bottom .leaflet-control { 152 | margin-bottom: 10px; 153 | } 154 | .leaflet-left .leaflet-control { 155 | margin-left: 10px; 156 | } 157 | .leaflet-right .leaflet-control { 158 | margin-right: 10px; 159 | } 160 | 161 | 162 | /* zoom and fade animations */ 163 | 164 | .leaflet-fade-anim .leaflet-tile { 165 | will-change: opacity; 166 | } 167 | .leaflet-fade-anim .leaflet-popup { 168 | opacity: 0; 169 | -webkit-transition: opacity 0.2s linear; 170 | -moz-transition: opacity 0.2s linear; 171 | -o-transition: opacity 0.2s linear; 172 | transition: opacity 0.2s linear; 173 | } 174 | .leaflet-fade-anim .leaflet-map-pane .leaflet-popup { 175 | opacity: 1; 176 | } 177 | .leaflet-zoom-animated { 178 | -webkit-transform-origin: 0 0; 179 | -ms-transform-origin: 0 0; 180 | transform-origin: 0 0; 181 | } 182 | .leaflet-zoom-anim .leaflet-zoom-animated { 183 | will-change: transform; 184 | } 185 | .leaflet-zoom-anim .leaflet-zoom-animated { 186 | -webkit-transition: -webkit-transform 0.25s cubic-bezier(0,0,0.25,1); 187 | -moz-transition: -moz-transform 0.25s cubic-bezier(0,0,0.25,1); 188 | -o-transition: -o-transform 0.25s cubic-bezier(0,0,0.25,1); 189 | transition: transform 0.25s cubic-bezier(0,0,0.25,1); 190 | } 191 | .leaflet-zoom-anim .leaflet-tile, 192 | .leaflet-pan-anim .leaflet-tile { 193 | -webkit-transition: none; 194 | -moz-transition: none; 195 | -o-transition: none; 196 | transition: none; 197 | } 198 | 199 | .leaflet-zoom-anim .leaflet-zoom-hide { 200 | visibility: hidden; 201 | } 202 | 203 | 204 | /* cursors */ 205 | 206 | .leaflet-interactive { 207 | cursor: pointer; 208 | } 209 | .leaflet-grab { 210 | cursor: -webkit-grab; 211 | cursor: -moz-grab; 212 | } 213 | .leaflet-crosshair, 214 | .leaflet-crosshair .leaflet-interactive { 215 | cursor: crosshair; 216 | } 217 | .leaflet-popup-pane, 218 | .leaflet-control { 219 | cursor: auto; 220 | } 221 | .leaflet-dragging .leaflet-grab, 222 | .leaflet-dragging .leaflet-grab .leaflet-interactive, 223 | .leaflet-dragging .leaflet-marker-draggable { 224 | cursor: move; 225 | cursor: -webkit-grabbing; 226 | cursor: -moz-grabbing; 227 | } 228 | 229 | /* marker & overlays interactivity */ 230 | .leaflet-marker-icon, 231 | .leaflet-marker-shadow, 232 | .leaflet-image-layer, 233 | .leaflet-pane > svg path, 234 | .leaflet-tile-container { 235 | pointer-events: none; 236 | } 237 | 238 | .leaflet-marker-icon.leaflet-interactive, 239 | .leaflet-image-layer.leaflet-interactive, 240 | .leaflet-pane > svg path.leaflet-interactive { 241 | pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */ 242 | pointer-events: auto; 243 | } 244 | 245 | /* visual tweaks */ 246 | 247 | .leaflet-container { 248 | background: #ddd; 249 | outline: 0; 250 | } 251 | .leaflet-container a { 252 | color: #0078A8; 253 | } 254 | .leaflet-container a.leaflet-active { 255 | outline: 2px solid orange; 256 | } 257 | .leaflet-zoom-box { 258 | border: 2px dotted #38f; 259 | background: rgba(255,255,255,0.5); 260 | } 261 | 262 | 263 | /* general typography */ 264 | .leaflet-container { 265 | font: 12px/1.5 "Helvetica Neue", Arial, Helvetica, sans-serif; 266 | } 267 | 268 | 269 | /* general toolbar styles */ 270 | 271 | .leaflet-bar { 272 | box-shadow: 0 1px 5px rgba(0,0,0,0.65); 273 | border-radius: 4px; 274 | } 275 | .leaflet-bar a, 276 | .leaflet-bar a:hover { 277 | background-color: #fff; 278 | border-bottom: 1px solid #ccc; 279 | width: 26px; 280 | height: 26px; 281 | line-height: 26px; 282 | display: block; 283 | text-align: center; 284 | text-decoration: none; 285 | color: black; 286 | } 287 | .leaflet-bar a, 288 | .leaflet-control-layers-toggle { 289 | background-position: 50% 50%; 290 | background-repeat: no-repeat; 291 | display: block; 292 | } 293 | .leaflet-bar a:hover { 294 | background-color: #f4f4f4; 295 | } 296 | .leaflet-bar a:first-child { 297 | border-top-left-radius: 4px; 298 | border-top-right-radius: 4px; 299 | } 300 | .leaflet-bar a:last-child { 301 | border-bottom-left-radius: 4px; 302 | border-bottom-right-radius: 4px; 303 | border-bottom: none; 304 | } 305 | .leaflet-bar a.leaflet-disabled { 306 | cursor: default; 307 | background-color: #f4f4f4; 308 | color: #bbb; 309 | } 310 | 311 | .leaflet-touch .leaflet-bar a { 312 | width: 30px; 313 | height: 30px; 314 | line-height: 30px; 315 | } 316 | .leaflet-touch .leaflet-bar a:first-child { 317 | border-top-left-radius: 2px; 318 | border-top-right-radius: 2px; 319 | } 320 | .leaflet-touch .leaflet-bar a:last-child { 321 | border-bottom-left-radius: 2px; 322 | border-bottom-right-radius: 2px; 323 | } 324 | 325 | /* zoom control */ 326 | 327 | .leaflet-control-zoom-in, 328 | .leaflet-control-zoom-out { 329 | font: bold 18px 'Lucida Console', Monaco, monospace; 330 | text-indent: 1px; 331 | } 332 | 333 | .leaflet-touch .leaflet-control-zoom-in, .leaflet-touch .leaflet-control-zoom-out { 334 | font-size: 22px; 335 | } 336 | 337 | 338 | /* layers control */ 339 | 340 | .leaflet-control-layers { 341 | box-shadow: 0 1px 5px rgba(0,0,0,0.4); 342 | background: #fff; 343 | border-radius: 5px; 344 | } 345 | .leaflet-control-layers-toggle { 346 | background-image: url(images/layers.png); 347 | width: 36px; 348 | height: 36px; 349 | } 350 | .leaflet-retina .leaflet-control-layers-toggle { 351 | background-image: url(images/layers-2x.png); 352 | background-size: 26px 26px; 353 | } 354 | .leaflet-touch .leaflet-control-layers-toggle { 355 | width: 44px; 356 | height: 44px; 357 | } 358 | .leaflet-control-layers .leaflet-control-layers-list, 359 | .leaflet-control-layers-expanded .leaflet-control-layers-toggle { 360 | display: none; 361 | } 362 | .leaflet-control-layers-expanded .leaflet-control-layers-list { 363 | display: block; 364 | position: relative; 365 | } 366 | .leaflet-control-layers-expanded { 367 | padding: 6px 10px 6px 6px; 368 | color: #333; 369 | background: #fff; 370 | } 371 | .leaflet-control-layers-scrollbar { 372 | overflow-y: scroll; 373 | overflow-x: hidden; 374 | padding-right: 5px; 375 | } 376 | .leaflet-control-layers-selector { 377 | margin-top: 2px; 378 | position: relative; 379 | top: 1px; 380 | } 381 | .leaflet-control-layers label { 382 | display: block; 383 | } 384 | .leaflet-control-layers-separator { 385 | height: 0; 386 | border-top: 1px solid #ddd; 387 | margin: 5px -10px 5px -6px; 388 | } 389 | 390 | /* Default icon URLs */ 391 | .leaflet-default-icon-path { 392 | background-image: url(images/marker-icon.png); 393 | } 394 | 395 | 396 | /* attribution and scale controls */ 397 | 398 | .leaflet-container .leaflet-control-attribution { 399 | background: #fff; 400 | background: rgba(255, 255, 255, 0.7); 401 | margin: 0; 402 | } 403 | .leaflet-control-attribution, 404 | .leaflet-control-scale-line { 405 | padding: 0 5px; 406 | color: #333; 407 | } 408 | .leaflet-control-attribution a { 409 | text-decoration: none; 410 | } 411 | .leaflet-control-attribution a:hover { 412 | text-decoration: underline; 413 | } 414 | .leaflet-container .leaflet-control-attribution, 415 | .leaflet-container .leaflet-control-scale { 416 | font-size: 11px; 417 | } 418 | .leaflet-left .leaflet-control-scale { 419 | margin-left: 5px; 420 | } 421 | .leaflet-bottom .leaflet-control-scale { 422 | margin-bottom: 5px; 423 | } 424 | .leaflet-control-scale-line { 425 | border: 2px solid #777; 426 | border-top: none; 427 | line-height: 1.1; 428 | padding: 2px 5px 1px; 429 | font-size: 11px; 430 | white-space: nowrap; 431 | overflow: hidden; 432 | -moz-box-sizing: border-box; 433 | box-sizing: border-box; 434 | 435 | background: #fff; 436 | background: rgba(255, 255, 255, 0.5); 437 | } 438 | .leaflet-control-scale-line:not(:first-child) { 439 | border-top: 2px solid #777; 440 | border-bottom: none; 441 | margin-top: -2px; 442 | } 443 | .leaflet-control-scale-line:not(:first-child):not(:last-child) { 444 | border-bottom: 2px solid #777; 445 | } 446 | 447 | .leaflet-touch .leaflet-control-attribution, 448 | .leaflet-touch .leaflet-control-layers, 449 | .leaflet-touch .leaflet-bar { 450 | box-shadow: none; 451 | } 452 | .leaflet-touch .leaflet-control-layers, 453 | .leaflet-touch .leaflet-bar { 454 | border: 2px solid rgba(0,0,0,0.2); 455 | background-clip: padding-box; 456 | } 457 | 458 | 459 | /* popup */ 460 | 461 | .leaflet-popup { 462 | position: absolute; 463 | text-align: center; 464 | margin-bottom: 20px; 465 | } 466 | .leaflet-popup-content-wrapper { 467 | padding: 1px; 468 | text-align: left; 469 | border-radius: 12px; 470 | } 471 | .leaflet-popup-content { 472 | margin: 13px 19px; 473 | line-height: 1.4; 474 | } 475 | .leaflet-popup-content p { 476 | margin: 18px 0; 477 | } 478 | .leaflet-popup-tip-container { 479 | width: 40px; 480 | height: 20px; 481 | position: absolute; 482 | left: 50%; 483 | margin-left: -20px; 484 | overflow: hidden; 485 | pointer-events: none; 486 | } 487 | .leaflet-popup-tip { 488 | width: 17px; 489 | height: 17px; 490 | padding: 1px; 491 | 492 | margin: -10px auto 0; 493 | 494 | -webkit-transform: rotate(45deg); 495 | -moz-transform: rotate(45deg); 496 | -ms-transform: rotate(45deg); 497 | -o-transform: rotate(45deg); 498 | transform: rotate(45deg); 499 | } 500 | .leaflet-popup-content-wrapper, 501 | .leaflet-popup-tip { 502 | background: white; 503 | color: #333; 504 | box-shadow: 0 3px 14px rgba(0,0,0,0.4); 505 | } 506 | .leaflet-container a.leaflet-popup-close-button { 507 | position: absolute; 508 | top: 0; 509 | right: 0; 510 | padding: 4px 4px 0 0; 511 | border: none; 512 | text-align: center; 513 | width: 18px; 514 | height: 14px; 515 | font: 16px/14px Tahoma, Verdana, sans-serif; 516 | color: #c3c3c3; 517 | text-decoration: none; 518 | font-weight: bold; 519 | background: transparent; 520 | } 521 | .leaflet-container a.leaflet-popup-close-button:hover { 522 | color: #999; 523 | } 524 | .leaflet-popup-scrolled { 525 | overflow: auto; 526 | border-bottom: 1px solid #ddd; 527 | border-top: 1px solid #ddd; 528 | } 529 | 530 | .leaflet-oldie .leaflet-popup-content-wrapper { 531 | zoom: 1; 532 | } 533 | .leaflet-oldie .leaflet-popup-tip { 534 | width: 24px; 535 | margin: 0 auto; 536 | 537 | -ms-filter: "progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678)"; 538 | filter: progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678); 539 | } 540 | .leaflet-oldie .leaflet-popup-tip-container { 541 | margin-top: -1px; 542 | } 543 | 544 | .leaflet-oldie .leaflet-control-zoom, 545 | .leaflet-oldie .leaflet-control-layers, 546 | .leaflet-oldie .leaflet-popup-content-wrapper, 547 | .leaflet-oldie .leaflet-popup-tip { 548 | border: 1px solid #999; 549 | } 550 | 551 | 552 | /* div icon */ 553 | 554 | .leaflet-div-icon { 555 | background: #fff; 556 | border: 1px solid #666; 557 | } 558 | 559 | 560 | /* Tooltip */ 561 | /* Base styles for the element that has a tooltip */ 562 | .leaflet-tooltip { 563 | position: absolute; 564 | padding: 6px; 565 | background-color: #fff; 566 | border: 1px solid #fff; 567 | border-radius: 3px; 568 | color: #222; 569 | white-space: nowrap; 570 | -webkit-user-select: none; 571 | -moz-user-select: none; 572 | -ms-user-select: none; 573 | user-select: none; 574 | pointer-events: none; 575 | box-shadow: 0 1px 3px rgba(0,0,0,0.4); 576 | } 577 | .leaflet-tooltip.leaflet-clickable { 578 | cursor: pointer; 579 | pointer-events: auto; 580 | } 581 | .leaflet-tooltip-top:before, 582 | .leaflet-tooltip-bottom:before, 583 | .leaflet-tooltip-left:before, 584 | .leaflet-tooltip-right:before { 585 | position: absolute; 586 | pointer-events: none; 587 | border: 6px solid transparent; 588 | background: transparent; 589 | content: ""; 590 | } 591 | 592 | /* Directions */ 593 | 594 | .leaflet-tooltip-bottom { 595 | margin-top: 6px; 596 | } 597 | .leaflet-tooltip-top { 598 | margin-top: -6px; 599 | } 600 | .leaflet-tooltip-bottom:before, 601 | .leaflet-tooltip-top:before { 602 | left: 50%; 603 | margin-left: -6px; 604 | } 605 | .leaflet-tooltip-top:before { 606 | bottom: 0; 607 | margin-bottom: -12px; 608 | border-top-color: #fff; 609 | } 610 | .leaflet-tooltip-bottom:before { 611 | top: 0; 612 | margin-top: -12px; 613 | margin-left: -6px; 614 | border-bottom-color: #fff; 615 | } 616 | .leaflet-tooltip-left { 617 | margin-left: -6px; 618 | } 619 | .leaflet-tooltip-right { 620 | margin-left: 6px; 621 | } 622 | .leaflet-tooltip-left:before, 623 | .leaflet-tooltip-right:before { 624 | top: 50%; 625 | margin-top: -6px; 626 | } 627 | .leaflet-tooltip-left:before { 628 | right: 0; 629 | margin-right: -12px; 630 | border-left-color: #fff; 631 | } 632 | .leaflet-tooltip-right:before { 633 | left: 0; 634 | margin-left: -12px; 635 | border-right-color: #fff; 636 | } 637 | -------------------------------------------------------------------------------- /js/pred/pred.js: -------------------------------------------------------------------------------- 1 | /* 2 | * CUSF Landing Prediction Version 2 3 | * Jon Sowman 2010 4 | * jon@hexoc.com 5 | * http://www.hexoc.com 6 | * 7 | * http://github.com/jonsowman/cusf-standalone-predictor 8 | * 9 | */ 10 | 11 | var map = null; 12 | 13 | // This function runs when the document object model is fully populated 14 | // and the page is loaded 15 | $(document).ready(function() { 16 | // Initialise the map canvas with parameters (lat, long, zoom-level) 17 | initMap(-34.03, 138.66, 8); 18 | 19 | // Populate the launch site list from sites.json 20 | populateLaunchSite(); 21 | 22 | // Setup all event handlers in the UI using jQuery 23 | setupEventHandlers(); 24 | 25 | // Initialise UI elements such as draggable windows 26 | initUI(); 27 | 28 | // Populate the launch card time/date fields 29 | initLaunchCard(); 30 | 31 | // Check if an old prediction is to be displayed, and process if so 32 | //displayOld(); 33 | 34 | // Read in URL parameters if provided, and run a prediction again. 35 | var params_provided = readURLParams(); 36 | 37 | // Plot the initial launch location 38 | plotClick(); 39 | 40 | // Initialise the burst calculator 41 | calc_init(); 42 | 43 | // Run the prediction if it is provided in the URL. 44 | if(params_provided) { 45 | runPrediction(); 46 | } 47 | }); 48 | 49 | 50 | function readURLParams() { 51 | // Get current URL 52 | url = new URL(window.location.href); 53 | 54 | var params_provided = false; 55 | 56 | if(url.searchParams.has('launch_latitude')){ 57 | $("#lat").val(url.searchParams.get('launch_latitude')); 58 | } 59 | if(url.searchParams.has('launch_longitude')){ 60 | $("#lon").val(url.searchParams.get('launch_longitude')); 61 | } 62 | if(url.searchParams.has('launch_altitude')){ 63 | $("#initial_alt").val(url.searchParams.get('launch_altitude')); 64 | } 65 | if(url.searchParams.has('ascent_rate')){ 66 | $("#ascent").val(url.searchParams.get('ascent_rate')); 67 | } 68 | if(url.searchParams.has('descent_rate')){ 69 | $("#drag").val(url.searchParams.get('descent_rate')); 70 | } 71 | if(url.searchParams.has('profile')){ 72 | $("#flight_profile").val(url.searchParams.get('profile')); 73 | } 74 | if(url.searchParams.has('prediction_type')){ 75 | $("#prediction_type").val(url.searchParams.get('prediction_type')); 76 | } 77 | if(url.searchParams.has('burst_altitude')){ 78 | $("#burst").val(url.searchParams.get('burst_altitude')); 79 | } 80 | if(url.searchParams.has('float_altitude')){ 81 | $("#burst").val(url.searchParams.get('float_altitude')); 82 | } 83 | 84 | if(url.searchParams.has('launch_datetime')){ 85 | var launch_datetime = url.searchParams.get('launch_datetime'); 86 | 87 | if(launch_datetime == "now"){ 88 | launch_moment = moment.utc(); 89 | time_was_now = true; 90 | } else { 91 | launch_moment = moment.utc(launch_datetime); 92 | } 93 | 94 | $("#min").val(launch_moment.minutes()); 95 | $("#hour").val(launch_moment.hours()); 96 | $("#day").val(launch_moment.date()); 97 | $("#month").val(launch_moment.month()+1); 98 | $("#year").val(launch_moment.year()); 99 | 100 | params_provided = true; 101 | } 102 | 103 | return params_provided; 104 | } 105 | 106 | // See if an old UUID was supplied in the hashstring 107 | // If it was, extract it, then populate the launch card with its parameters 108 | // then display the prediction 109 | function displayOld() { 110 | // Are we trying to display an old prediction? 111 | if( window.location.hash != "" ) { 112 | var ln = window.location.hash.split("="); 113 | var posteq = ln[1]; 114 | if ( posteq.length != 40 ) { 115 | throwError("The supplied hashstring was not a valid UUID."); 116 | appendDebug("The hashstring was not the expected length"); 117 | } else { 118 | current_uuid = posteq; 119 | appendDebug("Got an old UUID to plot:
" + current_uuid); 120 | appendDebug("Trying to populate form with scenario data..."); 121 | populateFormByUUID(current_uuid); 122 | appendDebug("Trying to get progress JSON"); 123 | $.getJSON("preds/"+current_uuid+"/progress.json", 124 | function(progress) { 125 | appendDebug("Got progress JSON from server for UUID"); 126 | if ( progress['error'] || !progress['pred_complete'] ) { 127 | appendDebug("The prediction was not completed" 128 | + " correctly, quitting"); 129 | } else { 130 | appendDebug("JSON said the prediction completed"); 131 | processCompletedPrediction(progress); 132 | writePredictionInfo(current_uuid, 133 | progress['run_time'], 134 | progress['dataset']); 135 | } 136 | }); 137 | } 138 | } 139 | } 140 | 141 | // A prediction has just been requested, so initialise the progress bar 142 | // and fade in the prediction progress window 143 | function predSub() { 144 | appendDebug(null, 1); // clear debug window 145 | appendDebug("Sending data to server..."); 146 | // Disable form 147 | $("#modelForm").find("input").attr("disabled", true); 148 | // Gets in the way of #status_message 149 | $("#error_window").fadeOut(250); 150 | // Initialise progress bar 151 | $("#prediction_status").html("Sending data to server..."); 152 | } 153 | 154 | // Make an AJAX request to the server and get the scenario information 155 | // for a given UUID, then populate the launch card with it 156 | function populateFormByUUID(pred_uuid) { 157 | $.get("ajax.php", { "action":"getModelByUUID", "uuid":pred_uuid }, function(data) { 158 | if ( !data.valid ) { 159 | appendDebug("Populating form by UUID failed"); 160 | appendDebug("The server said the model it made was invalid"); 161 | } else { 162 | // we're good to go, populate the form 163 | $("#lat").val(data.latitude); 164 | $("#lon").val(data.longitude); 165 | $("#initial_alt").val(data.altitude); 166 | $("#hour").val(data.hour); 167 | // we need to make minutes be "04" instead of "4" 168 | var scenario_minute = data.minute; 169 | if ( scenario_minute < 10 ) scenario_minute = "0" + scenario_minute; 170 | $("#min").val(scenario_minute); 171 | $("#second").val(data.second); 172 | $("#day").val(data.day); 173 | $("#month").attr("selectedIndex", data.month-1); 174 | $("#year").val(data.year); 175 | // we have to use [] notation for 176 | // values that have -s in them 177 | $("#ascent").val(data['ascent-rate']); 178 | $("#drag").val(data['descent-rate']); 179 | $("#burst").val(data['burst-altitude']); 180 | $("#software").val(data.software); 181 | // now sort the map out 182 | SetSiteOther(); 183 | plotClick(); 184 | } 185 | }, 'json'); 186 | } 187 | 188 | // Add information to the hashstring of the current window 189 | function addHashLink(link) { 190 | var ln = "#!/" + link; 191 | window.location = ln; 192 | } 193 | 194 | // Clear the Launch Site dropdown and repopulate it with the information from 195 | // sites.json, as well as an "Other" option to open the saved locations window 196 | function populateLaunchSite() { 197 | $("#site > option").remove(); 198 | $.getJSON("sites.json", function(sites) { 199 | $.each(sites, function(sitename, site) { 200 | $("