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 @@ - - - - - - -
- -
- - - + + + + diff --git a/html/index.html b/html/index.html index aaf7390..4e80e15 100644 --- a/html/index.html +++ b/html/index.html @@ -6,7 +6,6 @@ text { font: 12px sans-serif; } - th, td { padding-left: 8px; padding-right: 8px; @@ -42,7 +41,7 @@ tr:nth-child(odd) {background: #FFF} - Last update: . More information about this Galileo/GPS/BeiDou/Glonass open source monitor can be found here.
+ Last update: . More information about this Galileo/GPS/BeiDou/Glonass open source monitor can be found here. Live map here!. Contact me if you want access to the Grafana dashboard.

diff --git a/navdump.cc b/navdump.cc index 244888f..a1bc58d 100644 --- a/navdump.cc +++ b/navdump.cc @@ -125,11 +125,12 @@ try gm.tow = nmm.gi().gnsstow(); gmwtypes[{nmm.gi().gnsssv(), wtype}] = gm; + static map oldEph; cout << "gal inav for "<((uint8_t*)nmm.gpsi().contents().c_str(), nmm.gpsi().contents().size())); @@ -280,8 +284,10 @@ try cout<<"\n"; } else if(nmm.type() == NavMonMessage::BeidouInavTypeD1) { - if(skipBeidou) + if(skipBeidou) { + cout<((uint8_t*)nmm.bid1().contents().c_str(), nmm.bid1().contents().size())); @@ -361,8 +367,10 @@ try } else if(nmm.type() == NavMonMessage::GlonassInavType) { - if(skipGlonass) + if(skipGlonass) { + cout< gms; auto& gm = gms[nmm.gloi().gnsssv()]; diff --git a/navparse.cc b/navparse.cc index c02559c..66c04f8 100644 --- a/navparse.cc +++ b/navparse.cc @@ -169,6 +169,7 @@ struct SVIOD double getOmegaE() const { return 7.2921151467 * pow(10.0, -5.0);} // rad/s uint32_t getT0e() const { return t0e; } + uint32_t getT0c() const { return 60*t0c; } double getSqrtA() const { return ldexp(sqrtA, -19); } double getE() const { return ldexp(e, -33); } double getCuc() const { return ldexp(cuc, -29); } // radians @@ -253,7 +254,7 @@ struct SVStat bool sf1{0}, sf2{0}, sf3{0}, sf4{0}, sf5{0}; int BGDE1E5a{0}, BGDE1E5b{0}; bool e5bdvs{false}, e1bdvs{false}; - bool disturb1{false}, disturb2{false}, disturb3{false}, disturb4{false}, disturb5{false}; + uint16_t wn{0}; // we put the "unrolled" week number here! uint32_t tow{0}; // "last seen" // @@ -611,6 +612,24 @@ std::string humanTime(int gnssid, int wn, int tow) strftime(buffer, sizeof(buffer), "%a, %d %b %Y %T %z", &tm); return buffer; } +char getGNSSChar(int id) +{ + if(id==0) + return 'G'; + if(id==2) + return 'E'; + if(id==3) + return 'C'; + if(id==6) + return 'R'; + else + return '0'+id; +} + +std::string makeSatIDName(const SatID& satid) +{ + return fmt::sprintf("%c%02d@%d", getGNSSChar(satid.gnss), satid.sv, satid.sigid); +} std::optional getHzCorrection(time_t now, int src, unsigned int gnssid, unsigned int sigid, const svstats_t svstats) { @@ -639,19 +658,6 @@ std::optional getHzCorrection(time_t now, int src, unsigned int gnssid, return allHzCorr; } -char getGNSSChar(int id) -{ - if(id==0) - return 'G'; - if(id==2) - return 'E'; - if(id==3) - return 'C'; - if(id==6) - return 'R'; - else - return '0'+id; -} std::string humanBhs(int bhs) @@ -677,7 +683,7 @@ try H2OWebserver h2s("galmon"); - h2s.addHandler("/global", [](auto handler, auto req) { + h2s.addHandler("/global.json", [](auto handler, auto req) { nlohmann::json ret = nlohmann::json::object(); auto svstats = g_statskeeper.get(); ret["leap-seconds"] = g_dtLS; @@ -749,7 +755,7 @@ try return ret; }); - h2s.addHandler("/almanac", [](auto handler, auto req) { + h2s.addHandler("/almanac.json", [](auto handler, auto req) { auto beidoualma = g_beidoualmakeeper.get(); auto svstats = g_statskeeper.get(); nlohmann::json ret = nlohmann::json::object(); @@ -950,7 +956,7 @@ try return ret; }); - h2s.addHandler("/observers", [](auto handler, auto req) { + h2s.addHandler("/observers.json", [](auto handler, auto req) { nlohmann::json ret = nlohmann::json::array(); for(const auto& src : g_srcpos) { nlohmann::json obj; @@ -963,12 +969,154 @@ try obj["longitude"] = longlat.first; obj["latitude"] = longlat.second; obj["last-seen"] = src.second.lastSeen; + auto svstats = g_statskeeper.get(); + nlohmann::json svs = nlohmann::json::object(); + + for(const auto& sv : svstats) { + if(auto iter = sv.second.perrecv.find(src.first); iter != sv.second.perrecv.end()) { + if(iter->second.db > 0 && time(0) - iter->second.t < 120) { + nlohmann::json svo = nlohmann::json::object(); + svo["db"] = iter->second.db; + + svo["elev"] = iter->second.el; + svo["azi"] = iter->second.azi; + + Point sat; + + if((sv.first.gnss == 0 || sv.first.gnss == 2) && sv.second.completeIOD()) + getCoordinates(latestTow(sv.first.gnss, svstats), sv.second.liveIOD(), & sat); + if(sv.first.gnss == 3 && sv.second.oldBeidouMessage.sow >= 0 && sv.second.oldBeidouMessage.sqrtA != 0) { + getCoordinates(latestTow(sv.first.gnss, svstats), sv.second.oldBeidouMessage, &sat); + } + if(sv.first.gnss == 6) { + sat.x = sv.second.glonassMessage.x; + sat.y = sv.second.glonassMessage.y; + sat.z = sv.second.glonassMessage.z; + } + if(sat.x) { + Point our = g_srcpos[iter->first].pos; + svo["elev"] = getElevationDeg(sat, our); + svo["azi"] = getAzimuthDeg(sat, our); + } + + + svo["prres"] = iter->second.prres; + svo["age-s"] = time(0) - iter->second.t; + svo["last-seen"] = iter->second.t; + svo["gnss"] = sv.first.gnss; + svo["sv"] = sv.first.sv; + svo["sigid"] = sv.first.sigid; + + svs[makeSatIDName(sv.first)] = svo; + } + + } + } + obj["svs"]=svs; + ret.push_back(obj); } return ret; }); + + h2s.addHandler("/sv.json", [](auto handler, auto req) { + string_view path = convert(req->path); + + nlohmann::json ret = nlohmann::json::object(); + + SatID id; + auto pos = path.find("sv="); + if(pos == string::npos) { + return ret; + } + id.sv = atoi(&path[0] + pos+3); + + pos = path.find("gnssid="); + if(pos == string::npos) { + return ret; + } + id.gnss = atoi(&path[0]+pos+7); + id.sigid = 1; + pos = path.find("sigid="); + if(pos != string::npos) { + id.sigid = atoi(&path[0]+pos+7); + } + + + auto svstats = g_statskeeper.get(); + + ret["sv"] = id.sv; + ret["gnssid"] =id.gnss; + ret["sigid"] = id.sigid; + auto iter = svstats.find(id); + if(iter == svstats.end()) + return ret; + const auto& s= iter->second; + ret["a0"] = s.a0; + ret["a1"] = s.a1; + ret["a0g"] = s.a0g; + ret["a1g"] = s.a1g; + ret["sf1"] = s.sf1; + ret["sf2"] = s.sf2; + ret["sf3"] = s.sf3; + ret["sf4"] = s.sf4; + ret["sf5"] = s.sf5; + ret["ai0"] = s.ai0; + ret["ai1"] = s.ai1; + ret["ai2"] = s.ai2; + ret["BGDE1E5a"] = s.BGDE1E5a; + ret["BGDE1E5b"] = s.BGDE1E5b; + ret["wn"] = s.wn; + ret["tow"] = s.tow; + ret["dtLS"] = s.dtLS; + ret["dtLSF"] = s.dtLSF; + ret["wnLSF"] = s.wnLSF; + ret["dn"] = s.dn; + ret["e5bdvs"]=s.e5bdvs; + ret["e1bdvs"]=s.e1bdvs; + ret["e5bhs"]=s.e5bhs; + ret["e1bhs"]=s.e1bhs; + + + if(svstats[id].completeIOD()) { + const auto& iod = svstats[id].liveIOD(); + ret["iod"]= svstats[id].getIOD(); + ret["e"] = iod.getE(); + ret["omega"] = iod.getOmega(); + ret["sqrtA"]= iod.getSqrtA(); + ret["M0"] = iod.getM0(); + ret["i0"] = iod.getI0(); + ret["delta-n"] = iod.getDeltan(); + ret["omega-dot"] = iod.getOmegadot(); + ret["omega0"] = iod.getOmega0(); + ret["idot"] = iod.getIdot(); + ret["t0e"] = iod.getT0e(); + ret["t0c"] = iod.getT0c(); + ret["cuc"] = iod.getCuc(); + ret["cus"] = iod.getCus(); + ret["crc"] = iod.getCrc(); + ret["crs"] = iod.getCrs(); + ret["cic"] = iod.getCic(); + ret["cis"] = iod.getCis(); + ret["sisa"] = iod.sisa; + ret["af0"] = iod.af0; + ret["af1"] = iod.af1; + ret["af2"] = iod.af2; + Point p; + getCoordinates(s.tow, iod, &p); + ret["x"] = p.x; + ret["y"] = p.y; + ret["z"] = p.z; + auto longlat = getLongLat(p.x, p.y, p.z); + ret["longitude"] = 180.0*longlat.first/M_PI; + ret["latitude"] = 180.0*longlat.second/M_PI; + + } + return ret; + } + ); - h2s.addHandler("/svs", [](auto handler, auto req) { + h2s.addHandler("/svs.json", [](auto handler, auto req) { auto svstats = g_statskeeper.get(); nlohmann::json ret = nlohmann::json::object(); @@ -1202,7 +1350,7 @@ try item["wn"] = s.second.wn; item["tow"] = s.second.tow; - ret[fmt::sprintf("%c%02d@%d", getGNSSChar(s.first.gnss), s.first.sv, s.first.sigid)] = item; + ret[makeSatIDName(s.first)] = item; } return ret; }); @@ -1438,6 +1586,13 @@ try if(hours < 4) { idb.addValue(id, "eph-disco", disco); + g_svstats[id].latestDisco= disco; + g_svstats[id].latestDiscoAge= hours*3600; + } + else + g_svstats[id].latestDisco= -1; + + if(hours < 24) { idb.addValue(id, nanoTime(id.gnss, ent.second.wn, ent.second.tow), "eph-disco2", {{"meters", disco}, {"seconds", hours*3600.0}, @@ -1445,11 +1600,7 @@ try {"x", p.x}, {"y", p.y}, {"z", p.z}, {"oid", 1.0*ent.second.getIOD()}, {"oldoid", 1.0*ent.second.prevIOD.first}}); - g_svstats[id].latestDisco= disco; - g_svstats[id].latestDiscoAge= hours*3600; } - else - g_svstats[id].latestDisco= -1; @@ -1483,6 +1634,11 @@ try } else if(nmm.type() == NavMonMessage::RFDataType) { SatID id{nmm.rfd().gnssid(), nmm.rfd().gnsssv(), nmm.rfd().sigid()}; + + if(id.gnss==2 && id.sigid == 0) // old ubxtool output gets this wrong + id.sigid = 1; + + idb.addValue(id, nanoTime(0, nmm.rfd().rcvwn(), nmm.rfd().rcvtow()), "rfdata", {{"carrierphase", nmm.rfd().carrierphase()}, {"doppler", nmm.rfd().doppler()}, @@ -1527,7 +1683,7 @@ try time_t t = utcFromGPS(nmm.rfd().rcvwn(), nmm.rfd().rcvtow()); - if(t - g_svstats[id].perrecv[nmm.sourceid()].deltaHzTime > 10) { + if(t - g_svstats[id].perrecv[nmm.sourceid()].deltaHzTime > 10) { // only replace after 10 seconds g_svstats[id].perrecv[nmm.sourceid()].deltaHz = nmm.rfd().doppler() - res.preddop; g_svstats[id].perrecv[nmm.sourceid()].deltaHzTime = t; idb.addValue(id, "delta_hz", nmm.rfd().doppler() - res.preddop); @@ -1676,6 +1832,18 @@ try } else svstat.latestDisco = -1; + + if(hours < 24) { + idb.addValue(id, nanoTime(id.gnss, bm.wn, bm.sow), "eph-disco2", + {{"meters", jump.length()}, + {"seconds", hours*3600.0}, + {"oldx", oldpoint.x}, {"oldy", oldpoint.y}, {"oldz", oldpoint.z}, + {"x", newpoint.x}, {"y", newpoint.y}, {"z", newpoint.z}, + {"oid", 0}, + {"oldoid", 0}}); + } + + } } diff --git a/ubxtool.cc b/ubxtool.cc index 584417e..faa6717 100644 --- a/ubxtool.cc +++ b/ubxtool.cc @@ -515,6 +515,10 @@ int main(int argc, char** argv) if(version9) { cerr<<"Enabling UBX-NAV-SIG"<set_gnssid(2); // Galileo only for now + nmm.mutable_sr()->set_gnsssv(sv); + nmm.mutable_sr()->set_sigid(0); // we should fill this in later + nmm.mutable_sr()->set_type(payload[1]); + nmm.mutable_sr()->set_identifier(string((char*)payload.c_str()+4, 8)); + nmm.mutable_sr()->set_code(payload[12]); + nmm.mutable_sr()->set_params(string((char*)payload.c_str()+13, payload.size()-14)); + + string hexstring; + for(int n = 0; n < 15; ++n) + hexstring+=fmt::sprintf("%x", (int)getbitu(payload.c_str(), 36 + 4*n, 4)); + cerr<<"SAR RLM type "<