|
|
|
|
function gnssMeas = ProcessGnssMeas(gnssRaw)
|
|
|
|
|
% gnssMeas = ProcessGnssMeas(gnssRaw)
|
|
|
|
|
% Process raw measurements read from ReadGnssLogger
|
|
|
|
|
% Using technique explained in "Raw GNSS Measurements from Android" tutorial
|
|
|
|
|
%
|
|
|
|
|
% Input: gnssRaw, output from ReadGnssLogger
|
|
|
|
|
% Output: gnssMeas structure formatted conveniently for batch processing:
|
|
|
|
|
%gnssMeas.FctSeconds = Nx1 Full cycle time tag of M batched measurements.
|
|
|
|
|
% .ClkDCount = Nx1 Hw clock discontinuity count
|
|
|
|
|
% .HwDscDelS = Nx1 Hw clock change during each discontiuity (seconds)
|
|
|
|
|
% .Svid = 1xM all svIds found in gnssRaw.
|
|
|
|
|
% .AzDeg = 1xM azimuth in degrees at last valid epoch
|
|
|
|
|
% .ElDeg = 1xM elevation, ditto
|
|
|
|
|
% .tRxSeconds = NxM time of reception, seconds of gps week
|
|
|
|
|
% .tTxSeconds = NxM time of transmission, seconds of gps week
|
|
|
|
|
% .PrM = NxM pseudoranges, row i corresponds to FctSeconds(i)
|
|
|
|
|
% .PrSigmaM = NxM pseudorange error estimate (1-sigma)
|
|
|
|
|
% .DelPrM = NxM change in pr while clock continuous
|
|
|
|
|
% .PrrMps = NxM pseudorange rate
|
|
|
|
|
% .PrrSigmaMps= NxM
|
|
|
|
|
% .AdrM = NxM accumulated delta range (= -k * carrier phase)
|
|
|
|
|
% .AdrSigmaM = NxM
|
|
|
|
|
% .AdrState = NxM
|
|
|
|
|
% .Cn0DbHz = NxM
|
|
|
|
|
%
|
|
|
|
|
% all gnssMeas values are doubles
|
|
|
|
|
%
|
|
|
|
|
% Az and El are returned NaN. Compute Az,El using ephemeris and lla,
|
|
|
|
|
% or read from NMEA or GnssStatus
|
|
|
|
|
|
|
|
|
|
%Author: Frank van Diggelen
|
|
|
|
|
%Open Source code for processing Android GNSS Measurements
|
|
|
|
|
|
|
|
|
|
% Filter valid values first, so that rollover checks, etc, are on valid data
|
|
|
|
|
gnssRaw = FilterValid(gnssRaw);
|
|
|
|
|
|
|
|
|
|
%anything within 1ms is considered same epoch:
|
|
|
|
|
allRxMilliseconds = double(gnssRaw.allRxMillis);
|
|
|
|
|
gnssMeas.FctSeconds = (unique(allRxMilliseconds))*1e-3;
|
|
|
|
|
N = length(gnssMeas.FctSeconds);
|
|
|
|
|
gnssMeas.ClkDCount = zeros(N,1);
|
|
|
|
|
gnssMeas.HwDscDelS = zeros(N,1);
|
|
|
|
|
gnssMeas.Svid = unique(gnssRaw.Svid)'; %all the sv ids found in gnssRaw
|
|
|
|
|
M = length(gnssMeas.Svid);
|
|
|
|
|
gnssMeas.AzDeg = zeros(1,M)+NaN;
|
|
|
|
|
gnssMeas.ElDeg = zeros(1,M)+NaN;
|
|
|
|
|
gnssMeas.tRxSeconds = zeros(N,M)+NaN; %time of reception, seconds of gps week
|
|
|
|
|
gnssMeas.tTxSeconds = zeros(N,M)+NaN; %time of transmission, seconds of gps week
|
|
|
|
|
gnssMeas.PrM = zeros(N,M)+NaN;
|
|
|
|
|
gnssMeas.PrSigmaM = zeros(N,M)+NaN;
|
|
|
|
|
gnssMeas.DelPrM = zeros(N,M)+NaN;
|
|
|
|
|
gnssMeas.PrrMps = zeros(N,M)+NaN;
|
|
|
|
|
gnssMeas.PrrSigmaMps= zeros(N,M)+NaN;
|
|
|
|
|
gnssMeas.AdrM = zeros(N,M)+NaN;
|
|
|
|
|
gnssMeas.AdrSigmaM = zeros(N,M)+NaN;
|
|
|
|
|
gnssMeas.AdrState = zeros(N,M);
|
|
|
|
|
gnssMeas.Cn0DbHz = zeros(N,M)+NaN;
|
|
|
|
|
|
|
|
|
|
%GPS Week number:
|
|
|
|
|
weekNumber = floor(-double(gnssRaw.FullBiasNanos)*1e-9/GpsConstants.WEEKSEC);
|
|
|
|
|
|
|
|
|
|
%check for fields that are commonly all zero and may be missing from gnssRaw
|
|
|
|
|
if ~isfield(gnssRaw,'BiasNanos')
|
|
|
|
|
gnssRaw.BiasNanos = 0;
|
|
|
|
|
end
|
|
|
|
|
if ~isfield(gnssRaw,'TimeOffsetNanos')
|
|
|
|
|
gnssRaw.TimeOffsetNanos = 0;
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
%compute time of measurement relative to start of week
|
|
|
|
|
%subtract big longs (i.e. time from 1980) before casting time of week as double
|
|
|
|
|
WEEKNANOS = int64(GpsConstants.WEEKSEC*1e9);
|
|
|
|
|
weekNumberNanos = int64(weekNumber)*int64(GpsConstants.WEEKSEC*1e9);
|
|
|
|
|
|
|
|
|
|
%compute tRxNanos using gnssRaw.FullBiasNanos(1), so that
|
|
|
|
|
% tRxNanos includes rx clock drift since the first epoch:
|
|
|
|
|
tRxNanos = gnssRaw.TimeNanos -gnssRaw.FullBiasNanos(1) - weekNumberNanos;
|
|
|
|
|
|
|
|
|
|
%Assert if Tow state ~=1, because then gnssRaw.FullBiasNanos(1) might be wrong
|
|
|
|
|
State = gnssRaw.State(1);
|
|
|
|
|
assert(bitand(State,2^0) & bitand(State,2^3),...
|
|
|
|
|
'gnssRaw.State(1) must have bits 0 and 3 true before calling ProcessGnssMeas')
|
|
|
|
|
|
|
|
|
|
%tRxNanos now since beginning of the week, unless we had a week rollover
|
|
|
|
|
%assert(all(tRxNanos <= WEEKNANOS),'tRxNanos should be <= WEEKNANOS')
|
|
|
|
|
%TBD check week rollover code, and add assert tRxNanos <= WEEKNANOS after
|
|
|
|
|
assert(all(tRxNanos >= 0),'tRxNanos should be >= 0')
|
|
|
|
|
|
|
|
|
|
%subtract the fractional offsets TimeOffsetNanos and BiasNanos:
|
|
|
|
|
tRxSeconds = (double(tRxNanos)-gnssRaw.TimeOffsetNanos-gnssRaw.BiasNanos)*1e-9;
|
|
|
|
|
tTxSeconds = double(gnssRaw.ReceivedSvTimeNanos)*1e-9;
|
|
|
|
|
|
|
|
|
|
%check for week rollover in tRxSeconds
|
|
|
|
|
[prSeconds,tRxSeconds] = CheckGpsWeekRollover(tRxSeconds,tTxSeconds);
|
|
|
|
|
%we are ready to compute pseudorange in meters:
|
|
|
|
|
PrM = prSeconds*GpsConstants.LIGHTSPEED;
|
|
|
|
|
|
|
|
|
|
PrSigmaM = double(gnssRaw.ReceivedSvTimeUncertaintyNanos)*1e-9*...
|
|
|
|
|
GpsConstants.LIGHTSPEED;
|
|
|
|
|
PrrMps = gnssRaw.PseudorangeRateMetersPerSecond;
|
|
|
|
|
PrrSigmaMps = gnssRaw.PseudorangeRateUncertaintyMetersPerSecond;
|
|
|
|
|
AdrM = gnssRaw.AccumulatedDeltaRangeMeters;
|
|
|
|
|
AdrSigmaM = gnssRaw.AccumulatedDeltaRangeUncertaintyMeters;
|
|
|
|
|
AdrState = gnssRaw.AccumulatedDeltaRangeState;
|
|
|
|
|
Cn0DbHz = gnssRaw.Cn0DbHz;
|
|
|
|
|
|
|
|
|
|
%Now pack these vectors into the NxM matrices
|
|
|
|
|
for i=1:N %i is index into gnssMeas.FctSeconds and matrix rows
|
|
|
|
|
%get index of measurements within 1ms of this time tag
|
|
|
|
|
J = find(abs(gnssMeas.FctSeconds(i)*1e3 - allRxMilliseconds)<1);
|
|
|
|
|
for j=1:length(J) %J(j) is index into gnssRaw.*
|
|
|
|
|
k = find(gnssMeas.Svid==gnssRaw.Svid(J(j)));
|
|
|
|
|
%k is the index into gnssMeas.Svid and matrix columns
|
|
|
|
|
gnssMeas.tRxSeconds(i,k) = tRxSeconds(J(j));
|
|
|
|
|
gnssMeas.tTxSeconds(i,k) = tTxSeconds(J(j));
|
|
|
|
|
gnssMeas.PrM(i,k) = PrM(J(j));
|
|
|
|
|
gnssMeas.PrSigmaM(i,k) = PrSigmaM(J(j));
|
|
|
|
|
gnssMeas.PrrMps(i,k) = PrrMps(J(j));
|
|
|
|
|
gnssMeas.PrrSigmaMps(i,k)= PrrSigmaMps(J(j));
|
|
|
|
|
gnssMeas.AdrM(i,k) = AdrM(J(j));
|
|
|
|
|
gnssMeas.AdrSigmaM(i,k) = AdrSigmaM(J(j));
|
|
|
|
|
gnssMeas.AdrState(i,k) = AdrState(J(j));
|
|
|
|
|
gnssMeas.Cn0DbHz(i,k) = Cn0DbHz(J(j));
|
|
|
|
|
end
|
|
|
|
|
%save the hw clock discontinuity count for this epoch:
|
|
|
|
|
gnssMeas.ClkDCount(i) = gnssRaw.HardwareClockDiscontinuityCount(J(1));
|
|
|
|
|
|
|
|
|
|
if gnssRaw.HardwareClockDiscontinuityCount(J(1)) ~= ...
|
|
|
|
|
gnssRaw.HardwareClockDiscontinuityCount(J(end))
|
|
|
|
|
error('HardwareClockDiscontinuityCount changed within the same epoch');
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
gnssMeas = GetDelPr(gnssMeas);
|
|
|
|
|
|
|
|
|
|
end %of function ProcessGnssMeas
|
|
|
|
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|
|
|
|
|
|
|
|
|
function gnssRaw = FilterValid(gnssRaw)
|
|
|
|
|
%utility function for ProcessGnssMeas,
|
|
|
|
|
%remove fields corresponding to measurements that are invalid
|
|
|
|
|
%NOTE: this makes it simpler to process data. But it removes data,
|
|
|
|
|
% so if you want to investigate *why* fields are invalid, then do so
|
|
|
|
|
% before calling this function
|
|
|
|
|
|
|
|
|
|
%check ReceivedSvTimeUncertaintyNanos, PseudorangeRateUncertaintyMetersPerSecond
|
|
|
|
|
%for now keep only Svid with towUnc<0.5 microseconds and prrUnc < 10 mps
|
|
|
|
|
iTowUnc = gnssRaw.ReceivedSvTimeUncertaintyNanos > GnssThresholds.MAXTOWUNCNS;
|
|
|
|
|
iPrrUnc = gnssRaw.PseudorangeRateUncertaintyMetersPerSecond > ...
|
|
|
|
|
GnssThresholds.MAXPRRUNCMPS;
|
|
|
|
|
iBad = iTowUnc | iPrrUnc;
|
|
|
|
|
|
|
|
|
|
if any(iBad)
|
|
|
|
|
numBad = sum(iBad);
|
|
|
|
|
%assert if we're about to remove everything:
|
|
|
|
|
assert(numBad<length(iBad),'Removing all measurements in gnssRaw')
|
|
|
|
|
|
|
|
|
|
names = fieldnames(gnssRaw);
|
|
|
|
|
for i=1:length(names)
|
|
|
|
|
ts = sprintf('gnssRaw.%s(iBad) = [];',names{i});
|
|
|
|
|
eval(ts); %remove fields for invalid meas
|
|
|
|
|
end
|
|
|
|
|
%explain to user what happened:
|
|
|
|
|
fprintf('\nRemoved %d bad meas inside ProcessGnssMeas>FilterValid because:\n',...
|
|
|
|
|
sum(iBad))
|
|
|
|
|
if any(iTowUnc)
|
|
|
|
|
fprintf('towUnc > %.0f ns\n',GnssThresholds.MAXTOWUNCNS)
|
|
|
|
|
end
|
|
|
|
|
if any(iPrrUnc)
|
|
|
|
|
fprintf('prrUnc > %.0f m/s\n',GnssThresholds.MAXPRRUNCMPS)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
end %end of function FilterValid
|
|
|
|
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|
|
|
|
|
|
|
|
|
function gnssMeas = GetDelPr(gnssMeas)
|
|
|
|
|
% utility function for ProcessGnssMeas, compute DelPr
|
|
|
|
|
% gnssMeas.DelPrM = NxM, change in pr while clock is continuous
|
|
|
|
|
|
|
|
|
|
N = length(gnssMeas.FctSeconds);
|
|
|
|
|
M = length(gnssMeas.Svid);
|
|
|
|
|
|
|
|
|
|
bClockDis = [0;diff(gnssMeas.ClkDCount)~=0];%binary, 1 <=> clock discontinuity
|
|
|
|
|
|
|
|
|
|
%initialize first epoch to zero (by definition), rest to NaN
|
|
|
|
|
delPrM = zeros(N,M); delPrM(2:end,:) = NaN;
|
|
|
|
|
|
|
|
|
|
for j=1:M
|
|
|
|
|
i0=1; %i0 = index from where we compute DelPr
|
|
|
|
|
for i=2:N
|
|
|
|
|
if bClockDis(i) || isnan(gnssMeas.PrM(i0,j))
|
|
|
|
|
i0 = i; %reset to i if clock discont or a break in tracking
|
|
|
|
|
end
|
|
|
|
|
if bClockDis(i)
|
|
|
|
|
delPrM(i,j) = NaN;
|
|
|
|
|
else
|
|
|
|
|
delPrM(i,j) = gnssMeas.PrM(i,j) - gnssMeas.PrM(i0,j);
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
gnssMeas.DelPrM = delPrM;
|
|
|
|
|
end %of function GetDelPr
|
|
|
|
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|
|
|
|
|
|
|
|
|
function [prSeconds,tRxSeconds] = CheckGpsWeekRollover(tRxSeconds,tTxSeconds)
|
|
|
|
|
%utility function for ProcessGnssMeas
|
|
|
|
|
|
|
|
|
|
prSeconds = tRxSeconds - tTxSeconds;
|
|
|
|
|
|
|
|
|
|
iRollover = prSeconds > GpsConstants.WEEKSEC/2;
|
|
|
|
|
if any(iRollover)
|
|
|
|
|
fprintf('\nWARNING: week rollover detected in time tags. Adjusting ...\n')
|
|
|
|
|
prS = prSeconds(iRollover);
|
|
|
|
|
delS = round(prS/GpsConstants.WEEKSEC)*GpsConstants.WEEKSEC;
|
|
|
|
|
prS = prS - delS;
|
|
|
|
|
%prS are in the range [-WEEKSEC/2 : WEEKSEC/2];
|
|
|
|
|
%check that common bias is not huge (like, bigger than 10s)
|
|
|
|
|
maxBiasSeconds = 10;
|
|
|
|
|
if any(prS>maxBiasSeconds)
|
|
|
|
|
error('Failed to correct week rollover\n')
|
|
|
|
|
else
|
|
|
|
|
prSeconds(iRollover) = prS; %put back into prSeconds vector
|
|
|
|
|
%Now adjust tRxSeconds by the same amount:
|
|
|
|
|
tRxSeconds(iRollover) = tRxSeconds(iRollover) - delS;
|
|
|
|
|
fprintf('Corrected week rollover\n')
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
%TBD Unit test this
|
|
|
|
|
|
|
|
|
|
end %end of function CheckGpsWeekRollover
|
|
|
|
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
% Copyright 2016 Google Inc.
|
|
|
|
|
%
|
|
|
|
|
% Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
|
% you may not use this file except in compliance with the License.
|
|
|
|
|
% You may obtain a copy of the License at
|
|
|
|
|
%
|
|
|
|
|
% http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
%
|
|
|
|
|
% Unless required by applicable law or agreed to in writing, software
|
|
|
|
|
% distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
|
% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
|
% See the License for the specific language governing permissions and
|
|
|
|
|
% limitations under the License.
|
|
|
|
|
|
|
|
|
|
|