diff --git a/html/almanac.js b/html/almanac.js index 3dec536..fc06da5 100644 --- a/html/almanac.js +++ b/html/almanac.js @@ -58,13 +58,13 @@ function update() if(lastseen != null) d3.select("#freshness").html(lastseen.fromNow()); - d3.json("global", function(d) { + d3.json("global.json", function(d) { lastseen = moment(1000*d["last-seen"]); d3.select("#freshness").html(lastseen.fromNow()); }); - d3.json("almanac", function(d) { + d3.json("almanac.json", function(d) { // put data in an array sats=d; var arr=[]; @@ -86,9 +86,6 @@ function update() var livearr=[]; for(n = 0 ; n < arr.length; n++) { - if(arr[n].sv[0]!='G') - continue; - livearr.push(arr[n]); } diff --git a/html/doalles.js b/html/doalles.js index b70159a..7dd0664 100644 --- a/html/doalles.js +++ b/html/doalles.js @@ -22,13 +22,18 @@ function maketable(str, arr) .data(columns) .enter() .append("th") - .text(function(column) { + .html(function(column) { if(column == "delta_hz_corr") return "ΔHz"; if(column == "delta-gps") return "ΔGPS ns"; if(column == "delta-utc") return "ΔUTC ns"; + if(column == "sources") + return 'sources'; + if(column == "alma-dist") + return 'alma-dist'; + else return column; }); @@ -53,7 +58,7 @@ function maketable(str, arr) ret.value = ''; // ret.value=""; - ret.value += " "+row.sv; + ret.value += " "+row.sv+""; } else if(column == "aodc/e") { if(row["aodc"] != null && row["aode"] != null) @@ -168,13 +173,13 @@ function update() d3.select("#freshness").html(lastseen.fromNow()); - d3.json("global", function(d) { + d3.json("global.json", function(d) { d3.select('#facts').html("Galileo-UTC offset: "+d["utc-offset-ns"].toFixed(2)+" ns, Galileo-GPS offset: "+d["gps-offset-ns"].toFixed(2)+" ns, GPS UTC offset: "+d["gps-utc-offset-ns"].toFixed(2)+". "+d["leap-seconds"]+" leap seconds"); lastseen = moment(1000*d["last-seen"]); d3.select("#freshness").html(lastseen.fromNow()); }); - d3.json("svs", function(d) { + d3.json("svs.json", function(d) { // put data in an array sats=d; var arr=[]; @@ -186,7 +191,8 @@ function update() o.elev=""; Object.keys(o.perrecv).forEach(function(k) { if(o.perrecv[k]["last-seen-s"] < 1800) { - o.sources = o.sources + k +" "; + o.sources = o.sources + ''+k+' '; + o.db = o.db + o.perrecv[k].db +" "; if(o.perrecv[k].elev != null) o.elev = o.elev + o.perrecv[k].elev.toFixed(0)+" "; @@ -203,7 +209,7 @@ function update() if(o.prres == null) o.prres =""; if(o.perrecv[k].prres != null) - o.prres = o.prres + o.perrecv[k].prres.toFixed(1)+" "; + o.prres = o.prres + o.perrecv[k].prres.toFixed(0)+" "; else o.prres = o.prres + "_ "; diff --git a/html/geo/geo.css b/html/geo/geo.css new file mode 100644 index 0000000..daf3e55 --- /dev/null +++ b/html/geo/geo.css @@ -0,0 +1,128 @@ +body { + height: 100%; + margin: 0; + width: 100%; + float: left; + background-color: #ccc; + font-family: verdana, helvetica, arial, sans-serif; +} + +#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: 10px; +} + +#combined { + display: inline-block; + position: relative; + background-color: transparent; + // background-color: yellow; + height: 700px; + width: 100%; + //margin-left: auto; + //margin-right: auto; + margin: 10px; +} + +#svgworld { + position: absolute; top: 0; right: 0; bottom: 0; left: 0; + // border: blue 1px solid; + display: inline-block; + // margin-left: auto; + // margin-right: auto; +} + +#svggraticule { + position: absolute; top: 0; right: 0; bottom: 0; left: 0; + // border: blue 1px solid; + display: inline-block; + // margin-left: auto; + // margin-right: auto; +} + +#svgobservers { + position: absolute; top: 0; right: 0; bottom: 0; left: 0; + // border: blue 1px solid; + display: inline-block; + // margin-left: auto; + // margin-right: auto; +} + +#svgalmanac { + position: absolute; top: 0; right: 0; bottom: 0; left: 0; + // border: blue 1px solid; + display: inline-block; + // margin-left: auto; + // margin-right: auto; +} + +svg { + // border: red 1px solid; + // background-color: transparent; + padding: 20px; + // margin-left: auto; + // margin-right: auto; +} + +path.countries { + stroke-width: 1; + stroke: #75739F; + fill: #5EAFC6; +} +path.coverage { + stroke-width: 1; + stroke: #888; + fill: #888; + fill-opacity: 0.1; +} +circle.sats { + stroke-width: 1; + stroke: #4F442B; + //fill: #FCBC34; +} +text.labels { + // stroke-width: 1; + // stroke: #4F442B; + // fill: #FCBC34; + 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; +} + diff --git a/html/geo/geo.js b/html/geo/geo.js index 1f1354d..daa2bc0 100644 --- a/html/geo/geo.js +++ b/html/geo/geo.js @@ -1,110 +1,385 @@ +// +// +// + +var fileWorld = "world.geojson"; +var fileAlmanac = "../almanac.json" // "https://galmon.eu/almanac" +var fileObservers = "../observers.json" // "https://galmon.eu/observers" + +var projectionChoice = "Fahey"; +var projectionChoice = "CylindricalStereographic"; +var projectionChoice = "Equirectangular"; +//var projectionChoice = "Aitoff"; + +// +// +// + +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; + +function draw_world(data_world) +{ + // console.log("draw_world() " + data_world.features.length); + + svgWorld.selectAll("path") + .data(data_world.features) + .enter() + .append("path") + .attr("class", "countries") + .attr("d", geoPath); +} + +function draw_graticule() +{ + // Graticule + var graticule = d3.geoGraticule(); + + // console.log("draw_graticule()"); + + 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); + + 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]) + ')') + }); + + 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) { + a.push(o); + } + }); + return a; +} + +function draw_almanac(data_almanac) +{ + var arr = get_almanac_valid(data_almanac); + + console.log("draw_almanac() " + arr.length); + + svgAlmanac.selectAll("circle") + .data(arr) + .enter() + .append("circle") + .attr("class", "sats") + .attr("r", 3) + .attr("cx", d => aProjection([d["eph-longitude"],d["eph-latitude"]])[0]) + .attr("cy", d => aProjection([d["eph-longitude"],d["eph-latitude"]])[1]) + .attr("fill", function(d) { + switch (d.gnssid) { + case 0: return "green"; // GPS + case 1: return "gray"; // SBAS - not coded + case 2: return "blue"; // Galileo + case 3: return "red"; // BeiDou + case 4: return "gray"; // IMES - not coded + case 5: return "gray"; // QZSS - not coded + case 6: return "yellow"; // GLONASS + default: return "magenta"; // - should not happen + } + }); + + svgAlmanac.selectAll("text") + .data(arr) + .enter() + .append("text") + .attr("class", "labels") + .text(d => d.sv) + .attr("dx", d => 5+aProjection([d["eph-longitude"],d["eph-latitude"]])[0]) + .attr("dy", d => 5+aProjection([d["eph-longitude"],d["eph-latitude"]])[1]) + .attr("fill", function(d) { + if (d.observed==true) + return "black"; + return "#666666"; + }) + .attr("font-weight", function(d) { + if (d.observed==true) + return "bold"; + return null; + }); +} + +function draw_observers(data_observers) +{ + console.log("draw_observers() " + data_observers.length); + + svgObservers.selectAll("rect") + .data(data_observers) + .enter() + .append("rect") + .attr("class", "sats") + .attr("width", 8) + .attr("height", 8) + .attr("x", d => aProjection([d["longitude"],d["latitude"]])[0]-4) + .attr("y", d => aProjection([d["longitude"],d["latitude"]])[1]-4) + .attr("fill", function(d) { return "black"; }); + +} + +function draw_observers_coverage(data_observers) +{ + var radius = 55; // XXX fix + var geoCircle = d3.geoCircle(); + svgObservers.selectAll("path") + .data(data_observers) + .enter() + .append("path") + .attr("class", "coverage") + .attr("d", function(r) { + // console.log([r["longitude"], r["latitude"]] + " = " + aProjection([r["longitude"], r["latitude"]])); + return geoPath(geoCircle.center([r["longitude"],r["latitude"]]).radius(radius)()); + }); + +} + +var display_observers_count = 0; + +function do_update_almanac(error, results) +{ + var data_almanac = results[0]; + var data_observers = results[1]; + + // console.log("do_update_almanac() " + Object.keys(data_almanac).length + " " + data_observers.length); + + if (display_observers_count == 0) { + // does not need that much updating! + svgObservers.html(""); + draw_observers(data_observers); + draw_observers_coverage(data_observers) + display_observers_count = 10; + } + display_observers_count--; + + // We write into the svgalmanac area - so clean it and rewrite it + svgAlmanac.html(""); + draw_almanac(data_almanac); +} var repeat; -var world={}; -d3.json("world.geojson", function(result) { - world=result; - update(); -}); - -var oldsats=[]; - -function update() +function do_timer() { - var seconds = 60; - clearTimeout(repeat); - repeat=setTimeout(update, 1000.0*seconds); + var seconds = 60; + clearTimeout(repeat); + repeat = setTimeout(do_timer, 1000.0*seconds); - d3.queue(1).defer(d3.json, "../almanac").defer(d3.json, "../observers").awaitAll(ready); - - function ready(error, results) - { - // 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 mapDiv = document.getElementById("map"); - - var center = [0,0]; - var scale = 150; - var offset = [mapDiv.clientWidth/2, mapDiv.clientHeight/2]; - - var aProjection = d3.geoEquirectangular().scale(scale).translate([mapDiv.clientWidth/2,mapDiv.clientHeight/2]); - var 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(world); - var hscale = scale*mapDiv.clientWidth / (bounds[1][0] - bounds[0][0]); - var vscale = scale*mapDiv.clientHeight / (bounds[1][1] - bounds[0][1]); - scale = (hscale < vscale) ? hscale : vscale; - var offset = [mapDiv.clientWidth - (bounds[0][0] + bounds[1][0])/2, - mapDiv.clientHeight - (bounds[0][1] + bounds[1][1])/2]; - - // new projection - aProjection = d3.geoEquirectangular().center(center) - .scale(scale).translate(offset); - - geoPath = d3.geoPath().projection(aProjection); - - var svg =d3.select("svg"); - svg.html(""); - - svg.attr("width", mapDiv.clientWidth); - svg.attr("height", mapDiv.clientHeight); - - - svg.selectAll("path").data(world.features) - .enter() - .append("path") - .attr("d", geoPath) - .attr("class", "countries"); - - var arr=[]; - Object.keys(results[0]).forEach(function(e) { - var o = results[0][e]; - o.sv=e; - - if(o["eph-latitude"] != null) { - arr.push(o); - } - }); - - - svg.selectAll("circle").data(arr) - .enter() - .append("circle") - .attr("class", "sats") - .attr("r", 3) - .attr("cx", d => aProjection([d["eph-longitude"],d["eph-latitude"]])[0]) - .attr("cy", d => aProjection([d["eph-longitude"],d["eph-latitude"]])[1]) - .attr("fill", function(d) { if(d.gnssid==2) return "blue"; - else if(d.gnssid==3) return "red"; - else if(d.gnssid==6) return "yellow"; - else return "green"; }); - - svg.selectAll("text").data(arr) - .enter() - .append("text") - .attr("dx", d => 5+aProjection([d["eph-longitude"],d["eph-latitude"]])[0]) - .attr("dy", d => 5+aProjection([d["eph-longitude"],d["eph-latitude"]])[1]) - .text(d => d.sv) - .attr("fill", function(d) { if(d.observed==true) return "black"; return "#666666"; }) - .attr("font-weight", function(d) { if(d.observed==true) return "bold"; return null; }); - - svg.selectAll("rect").data(results[1]) - .enter() - .append("rect") - .attr("class", "sats") - .attr("width", 8) - .attr("height", 8) - .attr("x", d => aProjection([d["longitude"],d["latitude"]])[0]-4) - .attr("y", d => aProjection([d["longitude"],d["latitude"]])[1]-4) - .attr("fill", function(d) { console.log("Hallo!"); return "black"; }); - - } + d3.queue(1) + .defer(d3.json, fileAlmanac) + .defer(d3.json, fileObservers) + .awaitAll(do_update_almanac); } +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 = 210; // No idea what this does + + var idCombined = document.getElementById("combined"); + + svgWorld = d3.select("#svgworld"); + idWorld = document.getElementById("svgworld"); + + svgGraticule = d3.select("#svggraticule"); + idGraticule = document.getElementById("svggraticule"); + + switch(projectionChoice) { + default: + console.log(projectionChoice + ": not coded"); + // fall thru to Equirectangular + case 'Equirectangular': + aProjection = d3.geoEquirectangular() + .scale(scale) + .translate([idCombined.clientWidth/2,idCombined.clientHeight/2]); + break; + case 'Aitoff': + aProjection = d3.geoAitoff() + .scale(scale) + .translate([idCombined.clientWidth/2,idCombined.clientHeight/2]); + break; + case 'CylindricalStereographic': + aProjection = d3.geoCylindricalStereographic() + .scale(scale) + .translate([idCombined.clientWidth/2,idCombined.clientHeight/2]); + break; + case 'Fahey': + aProjection = d3.geoFahey() + .scale(scale) + .translate([idCombined.clientWidth/2,idCombined.clientHeight/2]); + break; + case 'Gilbert': + aProjection = d3.geoGilbert() + .scale(scale) + .translate([idCombined.clientWidth/2,idCombined.clientHeight/2]); + 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 / (bounds[1][0] - bounds[0][0]); + var vscale = scale*idCombined.clientHeight / (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(projectionChoice) { + default: + console.log(projectionChoice + ": not coded"); + // 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 'Gilbert': + aProjection = d3.geoGilbert() + .center(center) + .scale(scale) + .translate(offset); + break; + } + + } + + console.log("do_draw_world() " + "width=" + idCombined.clientWidth + "," + "height=" + idCombined.clientHeight); + + svgWorld.attr("width", idCombined.clientWidth); + svgWorld.attr("height", idCombined.clientHeight); + + svgObservers.attr("width", idCombined.clientWidth); + svgObservers.attr("height", idCombined.clientHeight); + + svgGraticule.attr("height", idCombined.clientHeight); + svgGraticule.attr("width", idCombined.clientWidth); + + svgAlmanac.attr("height", idCombined.clientHeight); + svgAlmanac.attr("width", idCombined.clientWidth); + +} + +function do_draw_world(data_world) +{ + // console.log("do_draw_world()"); + + set_projection(data_world); + + svgWorld.html(""); + draw_world(data_world); + + svgGraticule.html(""); + draw_graticule(); +} + +function read_world() +{ + // console.log("read_world()"); + d3.json(fileWorld, function(result) { + var 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_timer(); + }); +} + +function geo_start() +{ + // console.log("geo_start()"); + read_world(); +} + +geo_start(); + +// d3.select("body").onresize = do_timer; -// d3.select("body").onresize = update; diff --git a/html/geo/index.html b/html/geo/index.html index 24f0370..85bdf2a 100644 --- a/html/geo/index.html +++ b/html/geo/index.html @@ -1,68 +1,30 @@ - -
- - - - -