Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion console_backend/src/process_messages.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ use crate::log_panel;
use crate::shared_state::{EventType, SharedState, TabName};
use crate::tabs::{settings_tab, update_tab};
use crate::types::{
BaselineNED, Dops, GpsTime, MsgSender, ObservationMsg, PosLLH, Specan, UartState, VelNED,
BaselineNED, Dops, GpsTime, MsgSender, ObservationMsg, PosLLH, ProtectionLevel, Specan,
UartState, VelNED,
};
use crate::Tabs;

Expand Down Expand Up @@ -220,6 +221,9 @@ fn register_events(link: sbp::link::Link<Tabs>) {
link.register(|tabs: &Tabs, msg: Dops| {
tabs.solution_position.lock().unwrap().handle_dops(msg);
});
link.register(|tabs: &Tabs, msg: ProtectionLevel| {
tabs.solution_position.lock().unwrap().handle_prot_lvl(msg);
});
link.register(|tabs: &Tabs, msg: GpsTime| {
tabs.baseline.lock().unwrap().handle_gps_time(msg.clone());
tabs.solution_position.lock().unwrap().handle_gps_time(msg);
Expand Down
17 changes: 16 additions & 1 deletion console_backend/src/tabs/solution_tab/solution_position_tab.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@ use crate::output::{PosLLHLog, VelLog};
use crate::piksi_tools_constants::EMPTY_STR;
use crate::shared_state::SharedState;
use crate::tabs::solution_tab::LatLonUnits;
use crate::types::{Dops, GnssModes, GpsTime, PosLLH, RingBuffer, UtcDateTime, VelNED};
use crate::types::{
Dops, GnssModes, GpsTime, PosLLH, ProtectionLevel, RingBuffer, UtcDateTime, VelNED,
};
use crate::utils::{date_conv::*, *};

#[derive(Debug)]
Expand Down Expand Up @@ -420,6 +422,19 @@ impl SolutionPositionTab {
};
}

pub fn handle_prot_lvl(&mut self, msg: ProtectionLevel) {
let fields = msg.fields();
// Send protection level
let mut builder = Builder::new_default();
let msg = builder.init_root::<crate::console_backend_capnp::message::Builder>();
let mut solution_protection_level = msg.init_solution_protection_level();
solution_protection_level.set_lat(fields.lat);
solution_protection_level.set_lon(fields.lon);
solution_protection_level.set_hpl(fields.hpl);
self.client_sender
.send_data(serialize_capnproto_builder(builder));
}

/// Handle PosLLH / PosLLHDepA messages.
///
/// TODO(johnmichael.burke@) <https://swift-nav.atlassian.net/browse/CPP-95>
Expand Down
52 changes: 51 additions & 1 deletion console_backend/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ use sbp::link::Event;
use sbp::messages::{
navigation::{
MsgBaselineNed, MsgBaselineNedDepA, MsgDops, MsgDopsDepA, MsgGpsTime, MsgGpsTimeDepA,
MsgPosLlh, MsgPosLlhDepA, MsgVelNed, MsgVelNedDepA,
MsgPosLlh, MsgPosLlhDepA, MsgProtectionLevel, MsgProtectionLevelDepA, MsgVelNed,
MsgVelNedDepA,
},
observation::{
MsgObs, MsgObsDepB, MsgObsDepC, MsgOsr, PackedObsContent, PackedObsContentDepB,
Expand Down Expand Up @@ -1201,6 +1202,55 @@ impl Event for PosLLH {
}
}

/// Struct with shared fields for various Protection Level Message types.
#[allow(clippy::upper_case_acronyms)]
pub struct ProtectionLevelFields {
pub lat: f64,
pub lon: f64,
pub hpl: u16,
}

impl ProtectionLevelFields {
pub fn new(lat: f64, lon: f64, hpl: u16) -> Self {
Self { lat, lon, hpl }
}
}

/// Enum wrapping around various Protection Level Message types.
#[derive(Debug, Clone)]
#[allow(clippy::upper_case_acronyms)]
pub enum ProtectionLevel {
MsgProtLvl(MsgProtectionLevel),
MsgProtLvlDepA(MsgProtectionLevelDepA),
}

impl ProtectionLevel {
/// Return protection level fields
pub fn fields(&self) -> ProtectionLevelFields {
match self {
Self::MsgProtLvl(MsgProtectionLevel { lat, lon, hpl, .. })
| Self::MsgProtLvlDepA(MsgProtectionLevelDepA { lat, lon, hpl, .. }) => {
ProtectionLevelFields::new(*lat, *lon, *hpl)
}
}
}
}

impl Event for ProtectionLevel {
const MESSAGE_TYPES: &'static [u16] = &[
MsgProtectionLevel::MESSAGE_TYPE,
MsgProtectionLevelDepA::MESSAGE_TYPE,
];

fn from_sbp(msg: Sbp) -> Self {
match msg {
Sbp::MsgProtectionLevel(m) => Self::MsgProtLvl(m),
Sbp::MsgProtectionLevelDepA(m) => Self::MsgProtLvlDepA(m),
_ => unreachable!(),
}
}
}

/// Struct with shared fields for various Dops Message types.
pub struct DopsFields {
pub pdop: u16,
Expand Down
3 changes: 2 additions & 1 deletion resources/SolutionTabComponents/SolutionMapTab.qml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ Item {
id: solutionMap
WebChannel.id: "currPos"
signal recvPos(int id, double lat, double lng, double hAcc)
signal protPos(double lat, double lng, int hpl)
signal clearPos
}

Expand All @@ -29,6 +30,6 @@ Item {
webChannel: solutionMapChannel
url: Constants.solutionMap.pageURL
onCertificateError: error.ignoreCertificateError()
onJavaScriptConsoleMessage: (level, message, lineNumber, sourceID) => console.log("[MAP LOG] " + message)
onJavaScriptConsoleMessage: (level, message, lineNumber, sourceID) => console.log("[MAP LOG] @" + lineNumber + ": " + message)
}
}
103 changes: 93 additions & 10 deletions resources/web/map/js/trajectory_raw.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
import mapboxGlStyleSwitcher from 'https://cdn.skypack.dev/mapbox-gl-style-switcher';

const lines = ["#FF0000", "#FF00FF", "#00FFFF", "#0000FF", "#00FF00", "#000000"];
const LNG_KM = 111.320, LAT_KM = 110.574;

function decode(r){var n=r,t=[0,10,13,34,38,92],e=new Uint8Array(1.75*n.length|0),f=0,o=0,a=0;function i(r){o|=(r<<=1)>>>a,8<=(a+=7)&&(e[f++]=o,o=r<<7-(a-=8)&255)}for(var u=0;u<n.length;u++){var c,d=n.charCodeAt(u);127<d?(7!=(c=d>>>8&7)&&i(t[c]),i(127&d)):i(d)}r=new Uint8Array(e,0,f);var s=new TextDecoder().decode(r);while (s.slice(-1)=="\x00") s=s.slice(0,-1); return s;}

mapboxgl.accessToken = decode("@ACCESS_TOKEN@");
var map = new mapboxgl.Map({
container: 'map',
style: "mapbox://styles/mapbox/light-v11",
style: "mapbox://styles/mapbox/light-v11?optimize=true",
center: [-122.486052, 37.830348], // Initial focus coordinate
zoom: 16
zoom: 16,
performanceMetricsCollection: false,
});

var focusCurrent = false;
var startMarker = null;
var currentMarker = null;

class FocusToggle {
onAdd(map) {
Expand Down Expand Up @@ -43,6 +46,7 @@ map.addControl(new mapboxgl.NavigationControl());


var data = [];
var crumbCoords = [];

function setupData() {
data = [];
Expand All @@ -52,6 +56,7 @@ function setupData() {
features: []
});
}
crumbCoords = [];
}

setupData();
Expand All @@ -73,34 +78,94 @@ function setupLayers() {
source: `route${i}`,
paint: {
'fill-color': lines[i],
'fill-opacity': 0.2
'fill-opacity': 0.3,
'fill-outline-color': '#000000'
}
});
}
if (map.getSource('prot') == null) {
map.addSource('prot', {
type: 'geojson',
cluster: false,
data: {
type: 'FeatureCollection',
features: []
}
})
map.addLayer({
id: 'prot',
type: 'fill',
source: 'prot',
paint: {
'fill-color': "#00FF00",
'fill-opacity': 0.5
}
});
}
if (map.getSource('breadcrumb') == null) {
map.addSource('breadcrumb', {
type: 'geojson',
data: {
type: 'Feature',
geometry: {
type: "LineString",
coordinates: []
}
}
})
map.addLayer({
id: 'breadcrumb',
type: 'line',
source: 'breadcrumb',
layout: {
'line-join': 'round',
'line-cap': 'round'
},
paint: {
'line-color': '#888',
'line-width': 1
}
})
}
}

function syncCrumbCoords(){
map.getSource('breadcrumb').setData({
type: 'Feature',
geometry: {
type: 'LineString',
coordinates: crumbCoords
}
})
}

function syncLayers() {
// sync route datas with stored points
for (let i = 0; i < lines.length; i++) {
map.getSource(`route${i}`).setData(data[i]);
}
// clear protection, since its only one point and temporary
map.getSource('prot').setData({
type: 'FeatureCollection',
features: []
});
syncCrumbCoords();
}

const LNG_KM = 111.320, LAT_KM = 110.574;

/**
* Helper method to create elliptical geojson data
* @param center {[lng: number, lat: number]}
* @param rX horizontal radius in kilometers of ellipse
* @param rY vertical radius in kilometers of ellipse
* @param points optional number of points to render ellipse, higher for smoothness
* @return {{geometry: {coordinates: [][], type: string}, type: string}}
*/
function createGeoJsonEllipse(center, rX, rY, points = 32) {
function createGeoJsonEllipse(center, rX, rY) {
let coords = {latitude: center[1], longitude: center[0]};
let ret = [];
let dX = rX / (LNG_KM * Math.cos(coords.latitude * Math.PI / 180));
let dY = rY / LAT_KM;

let points = 16;
let theta, x, y;
for (let i = 0; i < points; i++) {
theta = (i / points) * (2 * Math.PI);
Expand Down Expand Up @@ -131,19 +196,37 @@ new QWebChannel(qt.webChannelTransport, (channel) => {
startMarker.remove();
startMarker = null;
}
if (currentMarker) {
currentMarker.remove();
currentMarker = null;
}
});

chn.recvPos.connect((id, lat, lng, hAcc) => {
const pos = [lat, lng],
rX = hAcc / 1000;
const pos = [lat, lng], rX = hAcc / 1000;
data[id].features.push(createGeoJsonEllipse(pos, rX, rX));
crumbCoords.push(pos);
if (!map) return;
if (!currentMarker) currentMarker = new mapboxgl.Marker().setLngLat(pos).addTo(map);
else currentMarker.setLngLat(pos);
if (!startMarker) {
startMarker = new mapboxgl.Marker().setLngLat(pos).addTo(map);
map.panTo(pos);
} else if (focusCurrent) map.panTo(pos);
let src = map.getSource(`route${id}`);
if (src != null) src.setData(data[id]);
if (src) src.setData(data[id]);

if (map.getSource('breadcrumb')) syncCrumbCoords();
})

chn.protPos.connect((lat, lng, hpl) => {
const pos = [lng, lat], rX = hpl / 100_000; // hpl in cm, convert to km
if (!map) return;
let src = map.getSource(`prot`);
if (src) src.setData({
type: 'FeatureCollection',
features: [createGeoJsonEllipse(pos, rX, rX)]
});
})
});

Expand Down
7 changes: 7 additions & 0 deletions src/main/resources/base/console_backend.capnp
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,12 @@ struct LoggingBarRecordingSize {
size @0 :UInt16;
}

struct SolutionProtectionLevel {
lat @0: Float64;
lon @1: Float64;
hpl @2: UInt16;
}

struct Message {
union {
solutionVelocityStatus @0 :SolutionVelocityStatus;
Expand Down Expand Up @@ -507,5 +513,6 @@ struct Message {
onTabChangeEvent @54 :TabChangeEvent;
loggingBarStartRecording @55 : LoggingBarStartRecording;
loggingBarRecordingSize @56 : LoggingBarRecordingSize;
solutionProtectionLevel @57: SolutionProtectionLevel;
}
}
3 changes: 3 additions & 0 deletions swiftnav_console/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,8 @@ def _process_message_buffer(self, buffer):
data[Keys.SOLUTION_LINE] = m.solutionPositionStatus.lineData
SolutionMap.send_pos(m.solutionPositionStatus)
SolutionPositionPoints.post_data_update(data)
elif m.which == Message.Union.SolutionProtectionLevel:
SolutionMap.send_prot_lvl(m.solutionProtectionLevel)
elif m.which == Message.Union.SolutionTableStatus:
data = solution_table_update()
data[Keys.ENTRIES][:] = [[entry.key, entry.val] for entry in m.solutionTableStatus.data]
Expand Down Expand Up @@ -754,6 +756,7 @@ def main(passed_args: Optional[Tuple[str, ...]] = None) -> int:
sys.argv.append("-qmljsdebugger=port:10002,block")
debug = QQmlDebuggingEnabler() # pylint: disable=unused-variable
QtCore.QCoreApplication.setAttribute(QtCore.Qt.ApplicationAttribute.AA_ShareOpenGLContexts)
QtCore.QCoreApplication.setAttribute(QtCore.Qt.ApplicationAttribute.AA_UseDesktopOpenGL)
QtWebEngineQuick.initialize()
app = QApplication(sys.argv)
app.setWindowIcon(QIcon(":/images/icon.ico"))
Expand Down
8 changes: 8 additions & 0 deletions swiftnav_console/solution_map.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,11 @@

class SolutionMap(QObject):
_instance: "SolutionMap"
# signal used to communicate (id, lat, lon, accuracy) points to map
recvPos: "Signal"
# signal used to communicate (lat, lon, hpl) to map
protPos: "Signal"
# signal used to reset map when disconnected
clearPos: "Signal"

def __init__(self):
Expand All @@ -38,6 +42,10 @@ def send_pos(cls, status) -> None:
for pos in data:
cls._instance.recvPos.emit(idx, pos.x, pos.y, status.hAcc)

@classmethod
def send_prot_lvl(cls, status) -> None:
cls._instance.protPos.emit(status.lat, status.lon, status.hpl)

@classmethod
def clear(cls) -> None:
cls._instance.clearPos.emit()