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(numBadFilterValid 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.