@@ -28,10 +28,20 @@ var receivers = [];
2828var recovery_names = [ ] ;
2929var recoveries = [ ] ;
3030
31+ var launchPredictions = { } ;
32+
3133var launches = null ;
3234var receiverCanvas = null ;
3335
3436var sondePrefix = [ "RS92" , "RS92-SGP" , "RS92-NGP" , "RS41" , "RS41-SG" , "RS41-SGP" , "RS41-SGM" , "DFM" , "DFM06" , "DFM09" , "DFM17" , "M10" , "M20" , "iMet-4" , "iMet-54" , "LMS6" , "LMS6-400" , "LMS6-1680" , "iMS-100" , "MRZ" , "chase" ] ;
37+ var sondeCodes = {
38+ "07" :"iMet-1" , "11" :"LMS6-403" , "13" :"RS92" , "14" :"RS92" , "17" :"DFM-09" , "18" :"DFM-06" , "19" :"MRZ-N1" , "22" :"RS-11G" , "23" :"RS41" , "24" :"RS41" , "34" :"iMet-4" , "35" :"iMS-100" , "41" :"RS41" , "42" :"RS41" , "52" :"RS92-NGP" ,
39+ "54" :"DFM-17" , "62" :"MRZ-3MK" , "63" :"M20" , "77" :"M10" , "82" :"LMS6-1680" , "84" :"iMet-54"
40+ } ;
41+ var unsupportedSondeCodes = {
42+ "15" :"PAZA-12M" , "16" :"PAZA-22" , "20" :"MK3" , "21" :"1524LA LORAN-C/GL5000" , "26" :"SRS-C34" , "27" :"AVK-MRZ" , "28" :"AVK–AK2-02" , "29" :"MARZ2-2" , "30" :"RS2-80" , "33" :"GTS1-2/GFE(L)" , "45" :"CF-06" , "58" :"AVK-BAR" ,
43+ "59" :"M2K2-R" , "68" :"AVK-RZM-2" , "69" :"MARL-A/Vektor-M-RZM-2" , "73" :"MARL-A" , "78" :"RS90" , "80" :"RS92" , "88" :"MARL-A/Vektor-M-MRZ" , "89" :"MARL-A/Vektor-M-BAR" , "97" :"iMet-2" , "99" :"iMet-2"
44+ } ;
3545
3646var got_positions = false ;
3747var zoomed_in = false ;
@@ -563,6 +573,34 @@ function load() {
563573 liveData ( ) ;
564574 } ;
565575
576+ L . NumberedDivIcon = L . Icon . extend ( {
577+ options : {
578+ iconUrl : host_url + markers_url + "marker_hole.png" ,
579+ number : '' ,
580+ shadowUrl : null ,
581+ iconSize : new L . Point ( 25 , 41 ) ,
582+ iconAnchor : new L . Point ( 13 , 41 ) ,
583+ popupAnchor : new L . Point ( 0 , - 33 ) ,
584+ className : 'leaflet-div-icon'
585+ } ,
586+
587+ createIcon : function ( ) {
588+ var div = document . createElement ( 'div' ) ;
589+ var img = this . _createImg ( this . options [ 'iconUrl' ] ) ;
590+ var numdiv = document . createElement ( 'div' ) ;
591+ numdiv . setAttribute ( "class" , "number" ) ;
592+ numdiv . innerHTML = this . options [ 'number' ] || '' ;
593+ div . appendChild ( img ) ;
594+ div . appendChild ( numdiv ) ;
595+ this . _setIconStyles ( div , 'icon' ) ;
596+ return div ;
597+ } ,
598+
599+ createShadow : function ( ) {
600+ return null ;
601+ }
602+ } ) ;
603+
566604 map . whenReady ( callBack ) ;
567605
568606 // animate-in the timebox,
@@ -609,39 +647,279 @@ function setTimeValue() {
609647 } , 100 ) ;
610648}
611649
650+ function launchSitePredictions ( times , station , properties , marker ) {
651+ var popup = launches . getLayer ( marker ) . getPopup ( ) ;
652+ var popupContent = popup . getContent ( ) ;
653+ var popupContentSplit = popupContent . split ( "<button onclick='launchSitePredictions(" ) [ 0 ] ;
654+ popupContentSplit += "<img style='width:60px;height:20px' src='img/hab-spinner.gif' />" ;
655+ popup . setContent ( popupContentSplit ) ;
656+ if ( popupContent . includes ( "Delete</button>" ) ) {
657+ deletePredictions ( marker ) ;
658+ popupContent = popupContent . split ( "<button onclick='deletePredictions(" ) [ 0 ] ;
659+ }
660+ times = times . split ( "," ) ;
661+ position = station . split ( "," ) ;
662+ properties = properties . split ( ":" ) ;
663+ var now = new Date ( ) ;
664+ var maxCount = 7
665+ var count = 0 ;
666+ var day = 0 ;
667+ var dates = [ ] ;
668+ while ( day < 8 ) {
669+ for ( var i = 0 ; i < times . length ; i ++ ) {
670+ var date = new Date ( ) ;
671+ var time = times [ i ] . split ( ":" ) ;
672+ if ( time [ 0 ] != 0 ) {
673+ date . setDate ( date . getDate ( ) + ( 7 + time [ 0 ] - date . getDay ( ) ) % 7 ) ;
674+ }
675+ date . setUTCHours ( time [ 1 ] ) ;
676+ date . setUTCMinutes ( time [ 2 ] ) ;
677+ date . setSeconds ( 0 ) ;
678+ date . setMilliseconds ( 0 ) ;
679+ date . setMinutes ( date . getMinutes ( ) - 45 ) ;
680+ while ( date < now ) {
681+ if ( time [ 0 ] == 0 ) {
682+ date . setDate ( date . getDate ( ) + 1 ) ;
683+ } else {
684+ date . setDate ( date . getDate ( ) + 7 ) ;
685+ }
686+ }
687+ if ( day > 0 ) {
688+ if ( time [ 0 ] == 0 ) {
689+ date . setDate ( date . getDate ( ) + day ) ;
690+ } else {
691+ date . setDate ( date . getDate ( ) + ( 7 * day ) ) ;
692+ }
693+ }
694+ if ( count < maxCount ) {
695+ if ( ( ( date - now ) / 36e5 ) < 170 ) {
696+ dates . push ( date . toISOString ( ) . split ( '.' ) [ 0 ] + "Z" ) ;
697+ count += 1 ;
698+ }
699+ }
700+ }
701+ day += 1 ;
702+ }
703+ dates . sort ( ) ;
704+ var completed = 0 ;
705+ for ( var i = 0 ; i < dates . length ; i ++ ) {
706+ var lon = ( ( 360 + ( position [ 1 ] % 360 ) ) % 360 )
707+ var url = "https://predict.cusf.co.uk/api/v1/?launch_latitude=" + position [ 0 ] + "&launch_longitude=" + lon + "&launch_datetime=" + dates [ i ] + "&ascent_rate=" + properties [ 0 ] + "&burst_altitude=" + properties [ 2 ] + "&descent_rate=" + properties [ 1 ] ;
708+ //var url = "https://api.v2.sondehub.org/tawhiri?launch_latitude=" + position[0] + "&launch_longitude=" + lon + "&launch_datetime=" + dates[i] + "&ascent_rate=" + properties[0] + "&burst_altitude=" + properties[2] + "&descent_rate=" + properties[1];
709+ showPrediction ( url ) . done ( handleData ) . fail ( handleError ) ;
710+ }
711+ function handleData ( data ) {
712+ completed += 1 ;
713+ plotPrediction ( data , dates , marker , properties ) ;
714+ if ( completed == dates . length ) {
715+ popupContent += "<button onclick='deletePredictions(" + marker + ")' style='margin-bottom:0;'>Delete</button>" ;
716+ popup . setContent ( popupContent ) ;
717+ }
718+ }
719+ function handleError ( error ) {
720+ completed += 1 ;
721+ if ( completed == dates . length ) {
722+ popupContent += "<button onclick='deletePredictions(" + marker + ")' style='margin-bottom:0;'>Delete</button>" ;
723+ popup . setContent ( popupContent ) ;
724+ }
725+ }
726+ }
727+
728+ function plotPrediction ( data , dates , marker , properties ) {
729+ if ( ! launchPredictions . hasOwnProperty ( marker ) ) {
730+ launchPredictions [ marker ] = { } ;
731+ }
732+ launchPredictions [ marker ] [ dates . indexOf ( data . request . launch_datetime ) + 1 ] = { } ;
733+ plot = launchPredictions [ marker ] [ dates . indexOf ( data . request . launch_datetime ) + 1 ] ;
734+
735+ ascent = data . prediction [ 0 ] . trajectory ;
736+ descent = data . prediction [ 1 ] . trajectory ;
737+ var predictionPath = [ ] ;
738+ for ( var i = 0 ; i < ascent . length ; i ++ ) {
739+ if ( ascent [ i ] . longitude > 180.0 ) {
740+ var longitude = ascent [ i ] . longitude - 360.0 ;
741+ } else {
742+ var longitude = ascent [ i ] . longitude ;
743+ }
744+ predictionPath . push ( [ ascent [ i ] . latitude , longitude ] ) ;
745+ } ;
746+ for ( var x = 0 ; x < descent . length ; x ++ ) {
747+ if ( descent [ x ] . longitude > 180.0 ) {
748+ var longitude = descent [ x ] . longitude - 360.0 ;
749+ } else {
750+ var longitude = descent [ x ] . longitude ;
751+ }
752+ predictionPath . push ( [ descent [ x ] . latitude , longitude ] ) ;
753+ } ;
754+ var burstPoint = ascent [ ascent . length - 1 ] ;
755+ var landingPoint = descent [ descent . length - 1 ] ;
756+
757+ plot . predictionPath = new L . polyline ( predictionPath , { color : 'red' } ) . addTo ( map ) ;
758+
759+ burstIconImage = host_url + markers_url + "balloon-pop.png" ;
760+
761+ burstIcon = new L . icon ( {
762+ iconUrl : burstIconImage ,
763+ iconSize : [ 20 , 20 ] ,
764+ iconAnchor : [ 10 , 10 ] ,
765+ } ) ;
766+
767+ if ( burstPoint . longitude > 180.0 ) {
768+ var burstLongitude = burstPoint . longitude - 360.0 ;
769+ } else {
770+ var burstLongitude = burstPoint . longitude ;
771+ }
772+
773+ plot . burstMarker = new L . marker ( [ burstPoint . latitude , burstLongitude ] , {
774+ icon : burstIcon
775+ } ) . addTo ( map ) ;
776+
777+ var burstTime = new Date ( burstPoint . datetime ) ;
778+ var burstTooltip = "<b>Time: </b>" + burstTime . toLocaleString ( ) + "<br><b>Altitude: </b>" + Math . round ( burstPoint . altitude ) + "m" ;
779+ plot . burstMarker . bindTooltip ( burstTooltip , { offset : [ 5 , 0 ] } ) ;
780+
781+ if ( landingPoint . longitude > 180.0 ) {
782+ var landingLongitude = landingPoint . longitude - 360.0 ;
783+ } else {
784+ var landingLongitude = landingPoint . longitude ;
785+ }
786+
787+ plot . landingMarker = new L . marker ( [ landingPoint . latitude , landingLongitude ] , {
788+ icon : new L . NumberedDivIcon ( { number : dates . indexOf ( data . request . launch_datetime ) + 1 } )
789+ } ) . addTo ( map ) ;
790+
791+ var landingTime = new Date ( landingPoint . datetime ) ;
792+ if ( properties [ 3 ] != "" && properties [ 4 ] != "" ) {
793+ var landingTooltip = "<b>Time:</b> " + landingTime . toLocaleString ( ) + "<br><b>Model Dataset:</b> " + data . request . dataset +
794+ "<br><b>Model Assumptions:</b><br>- " + data . request . ascent_rate + "m/s ascent<br>- " + data . request . burst_altitude + "m burst altitude (" + properties [ 3 ] + " samples)<br>- " + data . request . descent_rate + "m/s descent (" + properties [ 4 ] + " samples)" ;
795+ } else {
796+ var landingTooltip = "<b>Time:</b> " + landingTime . toLocaleString ( ) + "<br><b>Model Dataset:</b> " + data . request . dataset +
797+ "<br><b>Model Assumptions:</b><br>- " + data . request . ascent_rate + "m/s ascent<br>- " + data . request . burst_altitude + "m burst altitude<br>- " + data . request . descent_rate + "m/s descent" ;
798+ }
799+ plot . landingMarker . bindTooltip ( landingTooltip , { offset : [ 13 , - 28 ] } ) ;
800+ }
801+
802+ function showPrediction ( url ) {
803+ return $ . ajax ( {
804+ type : "GET" ,
805+ url : url ,
806+ dataType : "json" ,
807+ } ) ;
808+ }
809+
810+ function deletePredictions ( marker ) {
811+ if ( launchPredictions . hasOwnProperty ( marker ) ) {
812+ for ( var prediction in launchPredictions [ marker ] ) {
813+ if ( launchPredictions [ marker ] . hasOwnProperty ( prediction ) ) {
814+ for ( var object in launchPredictions [ marker ] [ prediction ] ) {
815+ if ( launchPredictions [ marker ] [ prediction ] . hasOwnProperty ( object ) ) {
816+ map . removeLayer ( launchPredictions [ marker ] [ prediction ] [ object ] ) ;
817+ }
818+ }
819+ }
820+ }
821+ }
822+ var popup = launches . getLayer ( marker ) . getPopup ( ) ;
823+ var popupContent = popup . getContent ( ) ;
824+ if ( popupContent . includes ( "Delete</button>" ) ) {
825+ popupContent = popupContent . split ( "<button onclick='deletePredictions(" ) [ 0 ] ;
826+ popup . setContent ( popupContent ) ;
827+ }
828+ }
829+
612830function showLaunchSites ( ) {
613831 if ( ! launches ) {
614832 launches = new L . LayerGroup ( ) ;
615833 $ . getJSON ( "launchSites.json" , function ( json ) {
616834 for ( var key in json ) {
617835 if ( json . hasOwnProperty ( key ) ) {
618836 var latlon = [ json [ key ] . lat , json [ key ] . lon ] ;
619- var sondes = json [ key ] . rs_types . toString ( ) ;
620- sondes = sondes . replace ( new RegExp ( "\\b07\\b" ) , "iMet-1 (possible to track)" ) ;
621- sondes = sondes . replace ( new RegExp ( "\\b11\\b" ) , "LMS6-403 (possible to track)" ) ;
622- sondes = sondes . replace ( new RegExp ( "\\b13\\b" ) , "RS92 (possible to track)" ) ;
623- sondes = sondes . replace ( new RegExp ( "\\b14\\b" ) , "RS92 (possible to track)" ) ;
624- sondes = sondes . replace ( new RegExp ( "\\b17\\b" ) , "DFM-09 (possible to track)" ) ;
625- sondes = sondes . replace ( new RegExp ( "\\b19\\b" ) , "MRZ-N1 (possible to track)" ) ;
626- sondes = sondes . replace ( new RegExp ( "\\b21\\b" ) , "RS-11G (possible to track)" ) ;
627- sondes = sondes . replace ( new RegExp ( "\\b22\\b" ) , "RS-11G (possible to track)" ) ;
628- sondes = sondes . replace ( new RegExp ( "\\b23\\b" ) , "RS41 (possible to track)" ) ;
629- sondes = sondes . replace ( new RegExp ( "\\b24\\b" ) , "RS41 (possible to track)" ) ;
630- sondes = sondes . replace ( new RegExp ( "\\b34\\b" ) , "iMet-4 (possible to track)" ) ;
631- sondes = sondes . replace ( new RegExp ( "\\b35\\b" ) , "iMS-100 (possible to track)" ) ;
632- sondes = sondes . replace ( new RegExp ( "\\b41\\b" ) , "RS41 (possible to track)" ) ;
633- sondes = sondes . replace ( new RegExp ( "\\b42\\b" ) , "RS41 (possible to track)" ) ;
634- sondes = sondes . replace ( new RegExp ( "\\b52\\b" ) , "RS92-NGP (possible to track)" ) ;
635- sondes = sondes . replace ( new RegExp ( "\\b54\\b" ) , "DFM-17 (possible to track)" ) ;
636- sondes = sondes . replace ( new RegExp ( "\\b62\\b" ) , "MRZ-3MK (possible to track)" ) ;
637- sondes = sondes . replace ( new RegExp ( "\\b63\\b" ) , "M20 (possible to track)" ) ;
638- sondes = sondes . replace ( new RegExp ( "\\b77\\b" ) , "M10 (possible to track)" ) ;
639- sondes = sondes . replace ( new RegExp ( "\\b82\\b" ) , "LMS6-1680 (possible to track)" ) ;
640- sondes = sondes . replace ( new RegExp ( "\\b84\\b" ) , "iMet-54 (possible to track)" ) ;
837+ var sondes = json [ key ] . rs_types ;
838+ var sondesList = "" ;
839+ for ( var y = 0 ; y < sondes . length ; y ++ ) {
840+ if ( Array . isArray ( sondes [ y ] ) == false ) {
841+ sondes [ y ] = [ sondes [ y ] ] ;
842+ }
843+ if ( sondeCodes . hasOwnProperty ( sondes [ y ] [ 0 ] ) ) {
844+ sondesList += sondeCodes [ sondes [ y ] [ 0 ] ]
845+ if ( sondes [ y ] . length > 1 ) {
846+ sondesList += " (" + sondes [ y ] [ 1 ] + " MHz)" ;
847+ }
848+ } else if ( unsupportedSondeCodes . hasOwnProperty ( sondes [ y ] [ 0 ] ) ) {
849+ sondesList += unsupportedSondeCodes [ sondes [ y ] [ 0 ] ] ;
850+ sondesList += " (cannot track)" ;
851+ } else {
852+ sondesList += sondes [ y ] [ 0 ] + " (unknown WMO code)" ;
853+ }
854+ if ( y < sondes . length - 1 ) {
855+ sondesList += ", " ;
856+ }
857+ }
641858 var marker = new L . circleMarker ( latlon , { color : '#696969' , fillColor : "white" , radius : 8 } ) ;
642- var popup = new L . popup ( { autoClose : false , closeOnClick : false } ) . setContent ( "<font style='font-size: 13px'>" + json [ key ] . station_name + "</font><br><br><b>Sondes launched:</b> " + sondes ) ;
859+ var popup = new L . popup ( { autoClose : false , closeOnClick : false } ) ;
643860 marker . bindPopup ( popup ) ;
644861 launches . addLayer ( marker ) ;
862+ if ( json [ key ] . hasOwnProperty ( 'times' ) ) {
863+ var popupContent = null ;
864+ popupContent = "<font style='font-size: 13px'>" + json [ key ] . station_name + "</font><br><br><b>Sondes launched:</b> " + sondesList ;
865+ var ascent_rate = 5 ;
866+ var descent_rate = 6 ;
867+ var burst_altitude = 26000 ;
868+ var burst_samples = "" ;
869+ var descent_samples = "" ;
870+ if ( json [ key ] . rs_types . includes ( "11" ) || json [ key ] . rs_types . includes ( "82" ) ) { //LMS6
871+ ascent_rate = 5 ;
872+ descent_rate = 2.5 ;
873+ burst_altitude = 33500 ;
874+ }
875+ if ( json [ key ] . hasOwnProperty ( 'ascent_rate' ) ) {
876+ ascent_rate = json [ key ] [ "ascent_rate" ] ;
877+ }
878+ if ( json [ key ] . hasOwnProperty ( 'descent_rate' ) ) {
879+ descent_rate = json [ key ] [ "descent_rate" ] ;
880+ }
881+ if ( json [ key ] . hasOwnProperty ( 'burst_altitude' ) ) {
882+ burst_altitude = json [ key ] [ "burst_altitude" ] ;
883+ }
884+ if ( json [ key ] . hasOwnProperty ( 'burst_samples' ) ) {
885+ burst_samples = json [ key ] [ "burst_samples" ] ;
886+ }
887+ if ( json [ key ] . hasOwnProperty ( 'descent_samples' ) ) {
888+ descent_samples = json [ key ] [ "descent_samples" ] ;
889+ }
890+ popupContent += "<br><b>Launch schedule:</b>" ;
891+ for ( var x = 0 ; x < json [ key ] [ 'times' ] . length ; x ++ ) {
892+ popupContent += "<br>- " ;
893+ var day = json [ key ] [ 'times' ] [ x ] . split ( ":" ) [ 0 ] ;
894+ if ( day == 0 ) {
895+ popupContent += "Everyday at " ;
896+ } else if ( day == 1 ) {
897+ popupContent += "Monday at " ;
898+ } else if ( day == 2 ) {
899+ popupContent += "Tuesday at " ;
900+ } else if ( day == 3 ) {
901+ popupContent += "Wednesday at " ;
902+ } else if ( day == 4 ) {
903+ popupContent += "Thursday at " ;
904+ } else if ( day == 5 ) {
905+ popupContent += "Friday at " ;
906+ } else if ( day == 6 ) {
907+ popupContent += "Saturday at " ;
908+ } else if ( day == 7 ) {
909+ popupContent += "Sunday at " ;
910+ }
911+ popupContent += json [ key ] [ 'times' ] [ x ] . split ( ":" ) [ 1 ] + ":" + json [ key ] [ 'times' ] [ x ] . split ( ":" ) [ 2 ] + " UTC" ;
912+ }
913+ if ( json [ key ] . hasOwnProperty ( 'notes' ) ) {
914+ popupContent += "<br><b>Notes:</b> " + json [ key ] [ "notes" ] ;
915+ }
916+ popupContent += "<br><b>Know when this site launches?</b> Contribute <a href='https://github.com/projecthorus/sondehub-tracker/issues/114' target='_blank'>here</a>" ;
917+ popupContent += "<br><button onclick='launchSitePredictions(\"" + json [ key ] [ 'times' ] . toString ( ) + "\", \"" + latlon . toString ( ) + "\", \"" + ascent_rate + ":" + descent_rate + ":" + burst_altitude + ":" + burst_samples + ":" + descent_samples + "\", \"" + launches . getLayerId ( marker ) + "\")' style='margin-bottom:0;'>Generate Predictions</button>" ;
918+ } else {
919+ popupContent = "<font style='font-size: 13px'>" + json [ key ] . station_name + "</font><br><br><b>Sondes launched:</b> " + sondesList ;
920+ popupContent += "<br><b>Know when this site launches?</b> Contribute <a href='https://github.com/projecthorus/sondehub-tracker/issues/114' target='_blank'>here</a>" ;
921+ }
922+ popup . setContent ( popupContent ) ;
645923 }
646924 }
647925 } ) ;
@@ -750,7 +1028,7 @@ function habitat_data(jsondata, alternative) {
7501028 "temperature_internal" : "°C" ,
7511029 "temperature_external" : "°C" ,
7521030 "temperature_radio" : "°C" ,
753- "pressure" : " Pa " ,
1031+ "pressure" : " hPa " ,
7541032 "voltage_solar_1" : " V" ,
7551033 "voltage_solar_2" : " V" ,
7561034 "battery_percent" : "%" ,
0 commit comments