partial commit of changes that should not break things in master

hzcorr
bert hubert 2019-09-22 16:42:23 +02:00
parent 4819e6a59a
commit 88a79489c0
9 changed files with 812 additions and 207 deletions

View File

@ -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]);
}

View File

@ -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 '<a href="observers.html">sources</a>';
if(column == "alma-dist")
return '<a href="almanac.html">alma-dist</a>';
else
return column;
});
@ -53,7 +58,7 @@ function maketable(str, arr)
ret.value = '<img width="16" height="16" src="https://ds9a.nl/tmp/'+ img +'"/>';
// ret.value="";
ret.value += "&nbsp;"+row.sv;
ret.value += "&nbsp;<a href='sv.html?gnssid=2&sv="+row.svid+"&sigid="+row.sigid+"'>"+row.sv+"</a>";
}
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: <b>"+d["utc-offset-ns"].toFixed(2)+"</b> ns, Galileo-GPS offset: <b>"+d["gps-offset-ns"].toFixed(2)+"</b> ns, GPS UTC offset: <b>"+d["gps-utc-offset-ns"].toFixed(2)+"</b>. "+d["leap-seconds"]+"</b> 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 + '<a href="observer.html?observer=' + k + '">'+k+'</a> ';
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 + "_ ";

128
html/geo/geo.css 100644
View File

@ -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;
}

View File

@ -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;

View File

@ -1,68 +1,30 @@
<html>
<head>
<style>
body,
svg {
height: 100%;
margin: 0;
width: 100%;
float: left;
}
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>galmon.eu geo</title>
<link rel="stylesheet" type="text/css" href="geo.css">
<script src="../d3.v4.min.js"></script>
<script src="../ext/topojson.v1.min.js"></script>
<script src="../ext/d3-geo-projection.v2.min.js"></script>
path.countries {
stroke-width: 1;
stroke: #75739F;
fill: #5EAFC6;
}
circle.sats {
stroke-width: 1;
stroke: #4F442B;
// fill: #FCBC34;
}
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.outline {
stroke: #9A8B7A;
}
</head>
<body>
<div id="galmongeo">
<h1>galmon.eu geo</h1>
<div id="galmoninfo">
This is a live map from the <a href="/">galmon.eu</a> project</p>
</div>
<div id="combined">
<svg id="svgworld"></svg>
<svg id="svggraticule"></svg>
<svg id="svgobservers"></svg>
<svg id="svgalmanac"></svg>
</div>
</div>
path.merged {
fill: #9A8B7A;
stroke: #4F442B;
stroke-width: 2px;
}
#map {
position: fixed;
left: 0px;
right: 0px;
top: 0px;
bottom: 0px;
}
</style>
</head>
<body>
<div id="map">
<svg width="100%" height="100%"></svg>
</div>
<script src="../d3.v4.min.js"></script>
<script src="geo.js"></script>
</body>
<script src="geo.js"></script>
</body>
</html>

View File

@ -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}
</style>
<body>
Last update: <span id="freshness"></span>. More information about this Galileo/GPS/BeiDou/Glonass open source monitor can be found <a href="https://github.com/ahupowerdns/galmon/blob/master/README.md#galmon">here</a>. <br/>
Last update: <span id="freshness"></span>. More information about this Galileo/GPS/BeiDou/Glonass open source monitor can be found <a href="https://github.com/ahupowerdns/galmon/blob/master/README.md#galmon">here</a>. Live map <a href="geo">here!</a>. Contact <a href="https://ds9a.nl/">me</a> if you want access to the Grafana dashboard.<br/>
<table id="svs"></table>
<hr>
<p>

View File

@ -125,11 +125,12 @@ try
gm.tow = nmm.gi().gnsstow();
gmwtypes[{nmm.gi().gnsssv(), wtype}] = gm;
static map<int,GalileoMessage> oldEph;
cout << "gal inav for "<<nmm.gi().gnssid()<<","<<nmm.gi().gnsssv()<<","<<nmm.gi().sigid()<<" tow "<< nmm.gi().gnsstow()<<" wtype "<< wtype<<" ";
static uint32_t tow;
if(wtype == 4) {
// 2^-34 2^-46
cout <<" af0 "<<gm.af0 <<" af1 "<<gm.af1 <<", scaled: "<<ldexp(1.0*gm.af0, 19-34)<<", "<<ldexp(1.0*gm.af1, 38-46);
cout <<" iodnav "<<gm.iodnav <<" af0 "<<gm.af0 <<" af1 "<<gm.af1 <<", scaled: "<<ldexp(1.0*gm.af0, 19-34)<<", "<<ldexp(1.0*gm.af1, 38-46);
if(tow && oldgm4s.count(nmm.gi().gnsssv()) && oldgm4s[nmm.gi().gnsssv()].iodnav != gm.iodnav) {
auto& oldgm4 = oldgm4s[nmm.gi().gnsssv()];
auto oldOffset = oldgm4.getAtomicOffset(tow);
@ -192,11 +193,14 @@ try
}
cout<<" "<<gmwtypes[{sv,9}].alma3.svid <<" af0 "<<gm.alma3.af0<<" af1 "<< gm.alma3.af1 <<" e5bhs "<< gm.alma3.e5bhs<<" e1bhs "<< gm.alma3.e1bhs <<" a0g " << gm.a0g <<" a1g "<< gm.a1g <<" t0g "<<gm.t0g <<" wn0g "<<gm.wn0g;
}
cout<<endl;
}
else if(nmm.type() == NavMonMessage::GPSInavType) {
if(skipGPS)
if(skipGPS) {
cout<<endl;
continue;
}
int sv = nmm.gpsi().gnsssv();
auto cond = getCondensedGPSMessage(std::basic_string<uint8_t>((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<<endl;
continue;
}
int sv = nmm.bid1().gnsssv();
auto cond = getCondensedBeidouMessage(std::basic_string<uint8_t>((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<<endl;
continue;
}
static map<int, GlonassMessage> gms;
auto& gm = gms[nmm.gloi().gnsssv()];

View File

@ -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<double> getHzCorrection(time_t now, int src, unsigned int gnssid, unsigned int sigid, const svstats_t svstats)
{
@ -639,19 +658,6 @@ std::optional<double> 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}});
}
}
}

View File

@ -515,6 +515,10 @@ int main(int argc, char** argv)
if(version9) {
cerr<<"Enabling UBX-NAV-SIG"<<endl; // satellite reception details
enableUBXMessageUSB(fd, 0x01, 0x43, 8);
cerr<<"Enabling UBX-RXM-MEASX"<<endl; // satellite reception details
enableUBXMessageUSB(fd, 0x02, 0x14, 1);
}
else {
cerr<<"Enabling UBX-NAV-SAT"<<endl; // satellite reception details
@ -936,8 +940,66 @@ int main(int argc, char** argv)
emitNMM(1, nmm);
}
}
else if(msg.getClass() == 1 && msg.getType() == 0x30) { // UBX-NAV-SVINFO
}
else if(msg.getClass() == 0x02 && msg.getType() == 0x14) { // UBX-RXM-MEASX
cerr<<"Got RXM-MEASX for "<<(int)payload[34]<<" satellites, r0 "<< (int)payload[30]<<" r1 " <<(int)payload[31]<<endl;
for(unsigned int n = 0 ; n < payload[34] ; ++n) {
uint16_t wholeChips;
uint16_t fracChips;
uint8_t intCodePhase = payload[64+24*n];
uint32_t codePhase;
memcpy(&wholeChips, &payload[56+24*n], 2);
memcpy(&fracChips, &payload[58+24*n], 2);
memcpy(&codePhase, &payload[60+24*n], 4);
cerr<<(int)payload[44+24*n]<<","<<(int)payload[45+24*n]<<" whole-chips "<<wholeChips<<" frac-chips "<<fracChips<<" int-code-phase " <<(int)intCodePhase <<" frac-code-phase "<<ldexp(codePhase, -21) << " mpath " << (int)payload[47+24*n] << " r1 " << (int)payload[66+24*n] << " r2 " <<(int)payload[67+24*n]<<endl;
}
}
else if(msg.getClass() == 0x02 && msg.getType() == 0x59) { // UBX-RXM-RLM
int type = (int)payload[1];
int sv = (int)payload[2];
NavMonMessage nmm;
nmm.set_sourceid(g_srcid);
nmm.set_localutcseconds(g_gstutc.tv_sec);
nmm.set_localutcnanoseconds(g_gstutc.tv_nsec);
nmm.set_type(NavMonMessage::SARResponseType);
nmm.mutable_sr()->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 "<<type<<" from gal sv " << sv << " beacon "<<hexstring <<" code "<<(int)payload[12]<<" params "<<payload[12] + 256*payload[13]<<endl;
// wk.emitLine(sv, "SAR "+hexstring);
// cout<<"SAR: sv = "<< (int)msg[2] <<" ";
// for(int n=4; n < 12; ++n)
// fmt::printf("%02x", (int)msg[n]);
// for(int n = 0; n < 15; ++n)
// fmt::printf("%x", (int)getbitu(msg.c_str(), 36 + 4*n, 4));
// cout << " Type: "<< (int) msg[12] <<"\n";
// cout<<"Parameter: (len = "<<msg.length()<<") ";
// for(unsigned int n = 13; n < msg.length(); ++n)
// fmt::printf("%02x ", (int)msg[n]);
// cout<<"\n";
}
else
cerr<<"Uknown UBX message of class "<<(int) msg.getClass() <<" and type "<< (int) msg.getType()<<endl;
// writen2(1, payload.d_raw.c_str(),msg.d_raw.size());
}