From e05fac32a1a118ff66f0f74a7a29682a81edd917 Mon Sep 17 00:00:00 2001 From: Martin Levy Date: Mon, 11 Nov 2019 19:11:59 -0800 Subject: [PATCH] geo2 - a fully functional graphical display with lots of controls --- geo2/geo.css | 268 +++++++++++++++ geo2/geo.html | 76 +++++ geo2/geo.js | 930 ++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 1274 insertions(+) create mode 100644 geo2/geo.css create mode 100644 geo2/geo.html create mode 100644 geo2/geo.js diff --git a/geo2/geo.css b/geo2/geo.css new file mode 100644 index 0000000..288cf82 --- /dev/null +++ b/geo2/geo.css @@ -0,0 +1,268 @@ +body { + height: 100%; + width: 100%; + margin: 0; + float: left; + background-color: #ccc; + font-family: verdana, helvetica, arial, sans-serif; +} + +h1 { + margin-top: 0px; + margin-bottom: 5px; + padding: 0px; +} + +#galmongeo { + border: blue 1px solid; + margin: 5px; + padding: 10px; + width: 98%; + background-color: white; + margin-left: auto; + margin-right: auto; +} + +#galmoninfo { + border-top: blue 1px solid; + margin-bottom: 0px; + display: block; +} +#galmontext { + margin-top: 5px; + margin-bottom: 5px; + float: left; + font-size: 20px; +} +#galmonchoice { + margin-top: 5px; + margin-bottom: 5px; + float: right; + font-size: 20px; +} + +#combined { + display: inline-block; + position: relative; + background-color: transparent; + height: 620px; + width: 100%; + //margin-left: auto; + //margin-right: auto; + margin: 0px; + padding: 0px; + border: gray 1px solid; +} + +#svgworld { + position: absolute; top: 0; right: 0; bottom: 0; left: 0; + display: inline-block; + // border: green 1px solid; +} + +#svggraticule { + position: absolute; top: 0; right: 0; bottom: 0; left: 0; + display: inline-block; + // border: blue 1px solid; +} + +#svgobservers { + position: absolute; top: 0; right: 0; bottom: 0; left: 0; + display: inline-block; + // border: blue 1px solid; +} + +#svgalmanac { + position: absolute; top: 0; right: 0; bottom: 0; left: 0; + display: inline-block; + // border: blue 1px solid; +} + +#controls { + position: absolute; bottom: 0; right: 0; + display: inline-block; + margin: 5px; +} +#rotation { + position: absolute; bottom: 0; left: 0; + display: inline-block; + margin: 5px; +} + +svg { + //padding: 20px; +} + +// #Coverage_95: { fill: #800; fill-opacity: 0.2; } +// #Coverage_95: { fill: #800; fill-opacity: 0.2; } +// #Coverage_95: { fill: #800; fill-opacity: 0.2; } +// #Coverage_98: { fill: #800; fill-opacity: 0.2; } +// #Coverage_99: { fill: #800; fill-opacity: 0.2; } + +path.countries { + background: yellow; + stroke-width: 1; + stroke: #75739F; + fill: #7ECFE6; +} +path.coverage { + stroke-width: 1; + stroke: #888; + fill: #888; + fill-opacity: 0.1; +} +path.coverage.down { + stroke-width: 0; + fill: transparent; + fill-opacity: 0; +} +path.observers { + stroke-width: 1; + stroke: black; + fill: black; + fill-opacity: .75; +} +path.observers.down { + stroke-width: 1; + stroke: red; + fill: red; + fill-opacity: .75; +} +text.observers { + font-family: courier new, courier; + font-style: italic; + font-size: 12px; + color: #888; +} +text.observers.down { + color: red; +} +circle.satellites { + stroke-width: 1; + stroke: #4F442B; +} +.radials { + stroke-width: 1; + stroke: red; + fill: none; +} +text.labels { + font-family: courier new, courier; + font-style: italic; +} +circle.centroid { + fill: #75739F; + pointer-events: none; +} +rect.bbox { + fill: none; + stroke-dasharray: 5 5; + stroke: #75739F; + stroke-width: 2; + pointer-events: none; +} +path.graticule { + fill: none; + stroke-width: 1; + stroke: #9A8B7A; +} +path.graticule.line { + stroke: #E5E1DE; +} +path.graticule.outline { + stroke: #9A8B7A; +} + +path.merged { + fill: #9A8B7A; + stroke: #4F442B; + stroke-width: 2px; +} +.tooltip { + font-family: courier new, courier; + font-style: italic; + font-size: 12px; + width: 200px; + position: relative; + background-color: #ddd; + border: solid; + border-width: 1px; + border-radius: 2px; + padding: 2px; +} + +.mybutton { + display: inline-block; + position: relative; + padding-left: 35px; + margin-top: 0px; + margin-bottom: 0px; + cursor: pointer; + font-size: 20px; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} +.checkmark { + position: absolute; + top: 0; + left: 0; + height: 25px; + width: 25px; + background-color: #eee; +} +.mybutton:hover input ~ .checkmark { + background-color: #ccc; +} +.mybutton input { + display: none; +} +.mybutton input:checked ~ .checkmark { + background-color: #2196F3; +} +.mybutton input:checked ~ .checkmark:after { + display: block; +} +.checkmark:after { + content: ""; + position: absolute; + display: none; +} +.mybutton .checkmark:after { + left: 9px; + top: 5px; + width: 5px; + height: 10px; + border: solid white; + border-width: 0 3px 3px 0; + -webkit-transform: rotate(45deg); + -ms-transform: rotate(45deg); + transform: rotate(45deg); +} +.rotatetable { + display: table; + border-collapse: separate; + border-spacing: 0px; +} +.rotatetable td { + vertical-align: middle; + padding: 0px 3px 0px 3px; + margin: 0px; +} +.myrotate { + cursor: pointer; + font-size: 20px; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} +#controls { + margin: 0px 5px 5px 0px; +} +#rotation { + margin: 0px 0px 5px 5px; + border: 1px gray solid; + background: #eee; +} diff --git a/geo2/geo.html b/geo2/geo.html new file mode 100644 index 0000000..b870a6c --- /dev/null +++ b/geo2/geo.html @@ -0,0 +1,76 @@ + + + + galmon.eu geo + + + + + + + + + + +
+

galmon.eu geo

+
+ + This is a live map from the galmon.eu project + + + +  |  + +  |  + + + + + + +
+
+ + + + + + +

+ + +
+ + + + + + + + + + + + + + + + + +
+
+
+
+ + + + diff --git a/geo2/geo.js b/geo2/geo.js new file mode 100644 index 0000000..8b17b72 --- /dev/null +++ b/geo2/geo.js @@ -0,0 +1,930 @@ +// +// +// + +var fileWorld = 'world.geojson'; +var fileAlmanac = '//galmon.eu/experimental/almanac.json' +var fileObservers = '//galmon.eu/experimental/observers.json' + +var projectionChoices = [ + 'Fahey', // The prefered one + 'Aitoff', // Interesting; but like Fahey +// 'Orthographic', // 3d globe - not perfectly coded for now XXX + 'CylindricalStereographic', // Beyond square + 'Equirectangular', // The boring one +] + +var projectionChoice = 0; + +// +// +// + +var svgWorld = d3.select('#svgworld'); +var idWorld = document.getElementById('svgworld'); + +var svgGraticule = d3.select('#svggraticule'); +var idGraticule = document.getElementById('svggraticule'); + +var svgObservers = d3.select('#svgobservers'); +var idObservers = document.getElementById('svgobservers'); + +var svgAlmanac = d3.select('#svgalmanac'); +var idAlmanac = document.getElementById('svgalmanac'); + +var geoPath; +var aProjection; + +var speed = 1e-2; +var start = now(); + +function now() +{ + return Math.round(Date.now() / 1000); +} + +function draw_world(data_world) +{ + svgWorld.html(""); + + svgWorld.selectAll("path") + .data(data_world.features) + .enter() + .append("path") + .attr("class", "countries") + .attr("d", geoPath); +} + +function draw_graticule() +{ + var graticule = d3.geoGraticule(); + + svgGraticule.html(""); + + svgGraticule.selectAll("path") + .data(graticule.lines()) + .enter() + .append("path") + .attr("class", "graticule line") + .attr("id", function(d) { + var c = d.coordinates; + if (c[0][0] == c[1][0]) { + return (c[0][0] < 0) ? -c[0][0] + "W" : +c[0][0] + "E"; + } else if (c[0][1] == c[1][1]) { + return (c[0][1] < 0) ? -c[0][1] + "S" : c[0][1] + "N"; + } + }) + .attr("d", geoPath); + var draw_text = false; + if (draw_text) { + svgGraticule.selectAll('text') + .data(graticule.lines()) + .enter() + .append("text") + .text(function(d) { + var c = d.coordinates; + if ((c[0][0] == c[1][0]) && (c[0][0] % 30 == 0)) { + return (c[0][0]); + } else if (c[0][1] == c[1][1]) { + return (c[0][1]); + } + }) + .attr("class", "label") + .attr("style", function(d) { + var c = d.coordinates; + return (c[0][1] == c[1][1]) ? "text-anchor: end" : "text-anchor: middle"; + }) + .attr("dx", function(d) { + var c = d.coordinates; + return (c[0][1] == c[1][1]) ? -10 : 0; + }) + .attr("dy", function(d) { + var c = d.coordinates; + return (c[0][1] == c[1][1]) ? 4 : 10; + }) + .attr('transform', function(d) { + var c = d.coordinates; + return ('translate(' + aProjection(c[0]) + ')') + }); + } + + var draw_outline = true; + if (draw_outline) { + svgGraticule.append("path") + .datum(graticule.outline) + .attr("class", "graticule outline") + .attr("d", geoPath); + } +} + +function get_almanac_valid(data_almanac) +{ + var a = []; + Object.keys(data_almanac).forEach(function(e) { + var o = data_almanac[e]; + o.sv = e; + if (o["eph-latitude"] != null) { + o.eph_latitude = o["eph-latitude"]; // json variables with dashes - bad bad + o.eph_longitude = o["eph-longitude"]; + a.push(o); + } + }); + return a; +} + +var Tooltip; + +function create_tooltop() +{ + // create a tooltip + Tooltip = d3.select("#combined") + .append("span") + .attr("class", "tooltip") + .attr("id", "tooltip") + .style("opacity", 0); +} + +function to2(num) +{ + return parseFloat(num).toFixed(2); +} + +function draw_radials(satellite, observer) +{ + // in order to draw the line between observer and satellite we need long/lat vs x/y + var observer_center = aProjection.invert(geoPath.centroid(observer)); // find the center of the projected rectangle + var satellite_center = aProjection.invert([satellite.attr("cx"), satellite.attr("cy")]); // center of satellite + svgObservers.append("path") + .attr("class", "radials") + .attr("d", function(r) { return geoPath({type: "LineString", coordinates: [observer_center, satellite_center]}); }); +} + +function draw_satellite_to_operator(d) +{ + // get list of observers for this satellite + satellite = d3.select("#Satellite_" + d.name); + a = observers_list_almanac_raw(d); + for (aa=0;aa r.sv) + .attr("class", "labels") + .attr("id", r => "Satellite_" + r.name + "_label") + .attr("dx", r => (aProjection([r.eph_longitude, r.eph_latitude])[0] + 0)) + .attr("dy", r => (aProjection([r.eph_longitude, r.eph_latitude])[1] + 0)) + .attr("fill", r => ((r.observed) ? "black" : "#666666")) + .attr("text-anchor", r => ((r.eph_longitude > 0) ? "start" : "end")) + .attr("baseline-shift", r => ((r.eph_latitude > 0) ? "+30%" : "-90%")) + .attr("fill-opacity", r => ((r.observed) ? "1.0" : ".5")) + .attr("stroke-opacity", r => ((r.observed) ? "1.0" : ".5")) + .attr("font-weight", r => ((r.observed) ? "bold" : null)); + + svgAlmanac.selectAll("circle") + .data(arr) + .enter() + .append("circle") + .attr("class", "satellites") + .attr("id", r => "Satellite_" + r.name) + .attr("r", 3) + .attr("cx", r => aProjection([r.eph_longitude, r.eph_latitude])[0]) + .attr("cy", r => aProjection([r.eph_longitude, r.eph_latitude])[1]) + .attr("fill", r => color_of(r)) + .attr("fill-opacity", r => ((r.observed) ? "1.0" : ".5")) + .attr("stroke-opacity", r => ((r.observed) ? "1.0" : ".5")) + .on("mouseover", mouseover) + .on("mousemove", mousemove) + .on("mouseleave", mouseleave); + +} + +function age_in_seconds(t) +{ + return now() - t; +} + +function a_to_s(a) +{ + var r = ""; + for (aa=0;aa 0) { + r = r.slice(0, -1); + } + return r; +} + +function svs_list_observer_raw(d) +{ + var r = [] + var svs = d.svs; + for (s in svs) { + // check the satellite is seen in almanac data - ie. double check. + if (data_almanac[svs[s].name]) { + r.push(svs[s].name); + } + } + return r; +} + +function observers_list_almanac_raw(d) +{ + var a = []; + var s_id = d.name; + + for (oo=0;oo considered_old) { + // data is old + return false; + } + if (Object.keys(r.svs).length == 0) { + // nothing visible + return false; + } + return true; +} + +var observer_shape = []; + +function draw_observers(data_observers) +{ + // Three function that change the tooltip when user hover / move / leave a cell + var mouseover = function(d) { + var o = svs_list_observer(d); + s = d.id + ": [" + to2(d.longitude) + "," + to2(d.latitude) + "]" + ((o == "") ? "" : " sees " + svs_list_observer(d)); + Tooltip.html(s) + .style("opacity", 1) + .style("left", (d3.mouse(this)[0] + tooltop_deltax) + "px") + .style("top", (d3.mouse(this)[1]) + "px"); + d3.selectAll(".radials").remove(); + draw_operator_to_satellite(d); + + // redraw only the one coverage circle + coverages = $("[id^='Coverage_']"); + coverages.hide(); + coverages = $("[id^='Coverage_" + d.id + "']"); + coverages.show(); + } + var mousemove = function(d) { + var o = svs_list_observer(d); + s = d.id + ": [" + to2(d.longitude) + "," + to2(d.latitude) + "]" + ((o == "") ? "" : " sees " + svs_list_observer(d)); + Tooltip.html(s) + .style("left", (d3.mouse(this)[0] + tooltop_deltax) + "px") + .style("top", (d3.mouse(this)[1]) + "px"); + } + var mouseleave = function(d) { + Tooltip.html("") + .style("opacity", 0); + d3.selectAll(".radials").remove(); + display_all_refresh(); + coverage_map_refresh(); + } + + // text first as we want the observer rectangle to be always above them! + svgAlmanac.selectAll("olables") + .data(data_observers) + .enter() + .append("text") + .text(r => r.id) + .attr("class", r => (observer_up(r) ? "observers" : "observers down")) + .attr("id", r => "Observer_" + r.id + "_label") + .attr("dx", r => (aProjection([r.longitude, r.latitude])[0])) + .attr("dy", r => (aProjection([r.longitude, r.latitude])[1])) + .attr("fill", r => (observer_up(r) ? "black" : "#666666")) + .attr("text-anchor", r => ((r.longitude > 0) ? "start" : "end")) + .attr("baseline-shift", r => ((r.latitude > 0) ? "+30%" : "-90%")) + .attr("fill-opacity", r => (observer_up(r) ? "1.0" : ".5")) + .attr("stroke-opacity", r => (observer_up(r) ? "1.0" : ".5")) + .attr("font-weight", r => (observer_up(r) ? "bold" : null)); + + // we draw a geo correct observer rectangle - mapped onto whatever globe we have projected + var observer_degrees = 2.5; + svgAlmanac.selectAll("div") + .data(data_observers) + .enter() + .append("path") + .attr("class", r => (observer_up(r) ? "observers" : "observers down")) + .attr("id", r => ("Observer_" + r.id)) + .attr("d", function(r) { + var path = { + type: "LineString", + coordinates: [ + [r.longitude - observer_degrees/2, r.latitude - observer_degrees/2], + [r.longitude - observer_degrees/2, r.latitude + observer_degrees/2], + [r.longitude + observer_degrees/2, r.latitude + observer_degrees/2], + [r.longitude + observer_degrees/2, r.latitude - observer_degrees/2], + ] + }; + observer_shape[r.id] = path; // save away for later + return geoPath(path); + }) + .on("mouseover", mouseover) + .on("mousemove", mousemove) + .on("mouseleave", mouseleave); + + // kick off the annimation - if needed + annimate_down_observers(); +} + +var down_timer; + +function annimate_down_observers() +{ + clearTimeout(down_timer); + + var down = document.getElementsByClassName('observers down'); + if (down.length > 0) { + // if we have an observer that is down - lets annoutate it! + for (var ii=0, ll=down.length; ii (observer_up(r) ? "coverage" : "coverage down")) + .attr("id", r => ("Coverage_" + r.id)) + .attr("d", function(r) { return geoPath(geoCircle.center([r.longitude, r.latitude]).radius(radius)()); + }); +} + +var data_almanac = null; +var data_observers = null; +var time_last_data_received = 0; + +function do_update_almanac_observers(error, results) +{ + var updated_almanac = false; + var updated_observers = false; + + if (results && results.length > 0 && results[0]) { + data_almanac = results[0]; + updated_almanac = true; + time_last_data_received = now(); + } + if (results.length > 1 && results[1]) { + data_observers = results[1]; + updated_observers = true; + time_last_data_received = now(); + } + + // XXX cheat for now - we need until we fix d3/svg bug above + updated_almanac = true; + updated_observers = true; + + if (updated_almanac) { + // We write into the svgalmanac area - so clean it and rewrite it + if (draw_almanac) { + draw_almanac(data_almanac); + } + // now hide/show the ones that should be seen + constellation_refresh(); + } + if (updated_observers) { + if (data_observers) { + draw_observers_coverage(data_observers) + draw_observers(data_observers); + } + coverage_map_refresh(); + observer_map_refresh(); + } + display_all_refresh(); +} + +var redisplay_timer = null; +var display_observers_count = 0; + +function stop_redisplay_timer() +{ + clearTimeout(redisplay_timer); + redisplay_timer = null; +} + +function do_redisplay_timer() +{ + stop_redisplay_timer(); + + if ((now() - time_last_data_received) >= 55) { + // refresh data from afar + if (display_observers_count == 0) { + // observers does not need that much updating! + d3.queue(1) + .defer(d3.json, fileAlmanac + '?t=' + now()) + .defer(d3.json, fileObservers + '?t=' + now()) + .awaitAll(do_update_almanac_observers); + display_observers_count = 10; + } else { + // just queue an update to almanac + d3.queue(1) + .defer(d3.json, fileAlmanac + '?t=' + now()) + .awaitAll(do_update_almanac_observers); + } + display_observers_count--; + } else { + // just use existing data + do_update_almanac_observers(null, []); + } + + var seconds = 60; + redisplay_timer = setTimeout(do_redisplay_timer, seconds * 1000.0); +} + +function set_projection(data_world) +{ + // var aProjection = d3.geoMercator().scale(100).translate([250, 250]); + // all this complexity is so we can scale to full screen. + // see: https://stackoverflow.com/questions/14492284/center-a-map-in-d3-given-a-geojson-object + + var center = [0, 0]; // This is very Euro-centric - but that's how these projections works. + var scale = 191; // No idea what this does + + var svgCombined = d3.select('#combined'); + var idCombined = document.getElementById("combined"); + + svgWorld = d3.select("#svgworld"); + idWorld = document.getElementById("svgworld"); + + svgGraticule = d3.select("#svggraticule"); + idGraticule = document.getElementById("svggraticule"); + + var offset = [idCombined.clientWidth/2, idCombined.clientHeight/2]; + + switch(projectionChoices[projectionChoice]) { + default: + // fall thru to Equirectangular + case 'Equirectangular': + aProjection = d3.geoEquirectangular() + .scale(scale) + .translate(offset); + break; + case 'Aitoff': + aProjection = d3.geoAitoff() + .scale(scale) + .translate(offset); + break; + case 'CylindricalStereographic': + aProjection = d3.geoCylindricalStereographic() + .scale(scale) + .translate(offset); + break; + case 'Fahey': + aProjection = d3.geoFahey() + .scale(scale) + .translate(offset); + break; + case 'Orthographic': + aProjection = d3.geoOrthographic() + .scale(scale) + .translate(offset); + break; + } + + geoPath = d3.geoPath() + .projection(aProjection); + + // using the path determine the bounds of the current map and use + // these to determine better values for the scale and translation + var bounds = geoPath.bounds(data_world); + var hscale = scale * (idCombined.clientWidth - 40) / (bounds[1][0] - bounds[0][0]); + var vscale = scale * (idCombined.clientHeight - 40) / (bounds[1][1] - bounds[0][1]); + scale = (hscale < vscale) ? hscale : vscale; + var offset = [ + idCombined.clientWidth - (bounds[0][0] + bounds[1][0])/2, + idCombined.clientHeight - (bounds[0][1] + bounds[1][1])/2 + ]; + + if (0) { + // new projection + switch(projectionChoices[projectionChoice]) { + default: + // fall thru to Equirectangular + case 'Equirectangular': + aProjection = d3.geoEquirectangular() + .center(center) + .scale(scale) + .translate(offset); + break; + case 'Aitoff': + aProjection = d3.geoAitoff() + .center(center) .scale(scale) + .translate(offset); + break; + case 'CylindricalStereographic': + aProjection = d3.geoCylindricalStereographic() + .center(center) + .scale(scale) + .translate(offset); + break; + case 'Fahey': + aProjection = d3.geoFahey() + .center(center) + .scale(scale) + .translate(offset); + break; + case 'Orthographic': + aProjection = d3.geoOrthographic() + .center(center) + .scale(scale) + .translate(offset); + break; + } + } + + svgCombined.attr("width", idCombined.clientWidth); + svgCombined.attr("height", idCombined.clientHeight); + + padding = 0 // 20 - see svg: padding in css + + svgWorld.attr("width", idCombined.clientWidth - padding * 2); + svgWorld.attr("height", idCombined.clientHeight - padding * 2); + + svgObservers.attr("width", idCombined.clientWidth - padding * 2); + svgObservers.attr("height", idCombined.clientHeight - padding * 2); + + svgGraticule.attr("width", idCombined.clientWidth - padding * 2); + svgGraticule.attr("height", idCombined.clientHeight - padding * 2); + + svgAlmanac.attr("width", idCombined.clientWidth - padding * 2); + svgAlmanac.attr("height", idCombined.clientHeight - padding * 2); + +} + +function do_draw_world(data_world) +{ + set_projection(data_world); + + aProjection.rotate([globe_rotate.lambda, globe_rotate.phi]); // show globe where it belongs for current timezone + + create_tooltop(); + + draw_world(data_world); + draw_graticule(); + + if (0) { + // XXX recode - this is bad. + if (projectionChoice == 'Orthographic') { + // rotating globe is done with a constant timer + d3.timer(function() { + var lambda = speed * (Date.now() - start); // λ + var phi = -15; // φ + aProjection.rotate([lambda + 180, -phi]); + + draw_world(data_world); + draw_graticule(); + do_update_almanac_observers(null, []) + }); + } + // XXX this will work one day ... + // var node = {id: "rotate_west"}; // The "id" is the only part used. + // rotate_globe(node) + } +} + +var data_world = null; + +function read_world() +{ + if (data_world == null) { + d3.json(fileWorld, function(result) { + data_world = result; + do_draw_world(data_world); + // after the world is read in and displayed - then start the timers! + // we don't redraw the world! + do_redisplay_timer(); + }); + } else { + // after the world is read in and displayed - then start the timers! + // we don't redraw the world! + do_draw_world(data_world); + do_redisplay_timer(); + } +} + +var globe_rotate = {lambda: 0.0, phi: 0.0}; +var globe_rotate_center = {lambda: 0.0, phi: 0.0}; + +function set_rotate_from_tz() +{ + var offset = new Date().getTimezoneOffset(); // in minutes from UTC + + globe_rotate_center.lambda = (offset/(60.0*24.0)) * 360.0; + globe_rotate_center.phi = 0.0; + + globe_rotate.lambda = globe_rotate_center.lambda; + globe_rotate.phi = globe_rotate_center.phi; +} + +function geo_start() +{ + set_rotate_from_tz(); + read_world(); +} + +geo_start(); + +// d3.select("body").onresize = do_redisplay_timer; + +var constellation_state = {G: true, E: true, C: true, I: false, J: false, R: true}; + +function constellation_click(node) +{ + // Satellites are named LETTER+NUMBERS + var c = node.innerText.trim(); + if (c == "GPS") { c = "G"; } // G = GPS (American) + else if (c == "Galileo") { c = "E"; } // E = Europe + else if (c == "BeiBou") { c = "C"; } // C = China + else if (c == "IMES") { c = "I"; } // I = Indoor QZSS (doubtfully seen) + else if (c == "QZSS") { c = "J"; } // J = Japan + else if (c == "GLONASS") { c = "R"; } // R = Russia + + satellites = $("[id^='Satellite_" + c + "']"); + + if (node.childNodes[1].checked) { + satellites.show(); + constellation_state[c] = true; + } else { + satellites.hide(); + constellation_state[c] = false; + } + d3.selectAll(".radials").remove(); + display_all_refresh(); +} + +function constellation_refresh() +{ + for (c in constellation_state) { + satellites = $("[id^='Satellite_" + c + "']"); + if (constellation_state[c]) { + satellites.show(); + } else { + satellites.hide(); + } + } +} + +var coverage_map_state = true; +var observer_map_state = true; + +function coverage_map_click(node) +{ + if (node.childNodes[1].checked) { + coverage_map_state = true; + } else { + coverage_map_state = false; + } + coverage_map_refresh(); +} + +function coverage_map_refresh() +{ + coverages = $("[id^='Coverage_']"); + if (coverage_map_state) { + coverages.show(); + } else { + coverages.hide(); + } +} + +function observer_map_click(node) +{ + if (node.childNodes[1].checked) { + observer_map_state = true; + } else { + observer_map_state = false; + } + observer_map_refresh(); +} + +function observer_map_refresh() +{ + observers = $("[id^='Observer_']"); + if (observer_map_state) { + observers.show(); + } else { + observers.hide(); + } +} + +var display_all_state = false; + +function display_all_click(node) +{ + if (node.childNodes[1].checked) { + display_all_state = true; + } else { + display_all_state = false; + } + display_all_refresh(); +} + +function display_all_refresh() +{ + if (display_all_state) { + for (d in data_observers) { + draw_operator_to_satellite(data_observers[d]); + } + } else { + d3.selectAll(".radials").remove(); + } +} + +function show_history_click(node) +{ + var c = node.innerText.trim(); + if (node.childNodes[1].checked) { + // XXX + console.log(c + " :checked"); + } else { + // XXX + console.log(c + " :not checked"); + } +} + +function show_animate_click(node) +{ + var c = node.innerText.trim(); + if (node.childNodes[1].checked) { + // XXX + console.log(c + " :checked"); + } else { + // XXX + console.log(c + " :not checked"); + } +} + +function update_projection_click(node) +{ + // cycle thru known globe projections + projectionChoice++; + if (projectionChoice >= projectionChoices.length) + projectionChoice = 0; + // now redraw everything! + read_world(); +} + +function rotate_globe(node) +{ + var t = node.id; + if (t == "rotate_north") { + globe_rotate.phi += 10; // North + } else if (t == "rotate_west") { + globe_rotate.lambda -= 5; // West + } else if (t == "rotate_center") { + globe_rotate.lambda = globe_rotate_center.lambda; + globe_rotate.phi = globe_rotate_center.phi; // Reset to center + } else if (t == "rotate_east") { + globe_rotate.lambda += 5; // East + } else if (t == "rotate_south") { + globe_rotate.phi -= 10; // South + } + + read_world(); +} + +// JQuery also has a startup + +$(document).ready(function () { + $("[id^='constilation']").click(function() { constellation_click(this); }); + $('#coverage_map').click(function() { coverage_map_click(this); }); + $('#observer_map').click(function() { observer_map_click(this); }); + + $('#display_all').click(function() { display_all_click(this); }); + $('#show_history').click(function() { show_history_click(this); }); + $('#show_animate').click(function() { show_animate_click(this); }); + $('#update_projection').click(function() { update_projection_click(this); }); + + $("[id^='rotate_']").click(function() { rotate_globe(this); }); +}); +