Main Content

Analyze GNSS Satellite Visibility

This example shows how to simulate and analyze GNSS satellite visibility at specified receiver positions and times using a GNSS/GPS ephemeris or almanac file. Use the live script controls to set various parameters for the satellite simulation.

Specify Simulation Parameters

Specify the navigation message file type, start time, duration in hours, and time between samples in seconds of the simulation. Also, specify the position of the receiver in geodetic coordinates and the mask angle, or minimum elevation angle, of the receiver.

gnssFileType = "RINEX";
startTime = datetime(2021,06,24,04,00,00);
numHours = 24;
dt =60; % s
latitude =42.3013162; % deg
longitude = -71.3782972; % deg
altitude = 50; % m
recPos = [latitude longitude altitude]; % [deg deg m]
maskAngle = 5; % deg

Display the GNSS receiver position.

geoplot(latitude,longitude,"x")
title("Receiver Position")

If using RINEX Navigation Message files, select the GNSS satellite system to analyze.

satSys = "GPS";
if gnssFileType == "RINEX"
    satSys = "GPS";
end

Get Satellite Orbital Parameters

Use the exampleHelperParseGNSSFile helper function to obtain the initial satellite orbital parameters and satellite IDs from a GNSS file. The GPS, Galileo, BeiDou, NavIC, and QZSS ephemeris data is valid for two hours before and after the time of ephemeris. The GLONASS and SBAS ephemeris data is valid for 15 minutes before and after the time of ephemeris. In those valid time windows, you can use the ephemeris data with the gnssconstellation function as an orbit propagator to determine the positions of the satellites in orbit with high precision. In contrast, while almanac data, is valid for a period of 90 days, you can only use it for estimating the approximate positions of the satellites.

[navmsg,satIDs] = exampleHelperParseGNSSFile(gnssFileType,satSys);

The files used in this example are tied to the specified start time startTime. To use different RINEX files, use the RINEX files included in the Read Navigation and Observation Data from RINEX File example. For examples of how to download and use different SEM and YUMA almanac files, see the Read GPS Navigation Message Data from SEM Almanac File and Read GPS Navigation Message Data from YUMA Almanac File, respectively.

Other orbit propagators are available in the satelliteScenario object.

Once you have new ephemeris data or an almanac file, update the file variable of the exampleHelperParseGNSSFile helper function for the case that corresponds to your file type.

Generate Satellite Visibilities

First, get the satellite positions and a matrix of logical values that determine their visibility at startTime. Plot the visible satellites using skyplot.

satPos = gnssconstellation(startTime,navmsg,GNSSFileType=gnssFileType);
[az,el,vis] = lookangles(recPos,satPos,maskAngle);
figure
skyplot(az(vis),el(vis),satIDs(vis),MaskElevation=maskAngle);

Note that the visibility of the satellites is determined from the WGS 84 ellipsoid reference and the maskAngle elevation cutoff. To simulate GNSS satellite visibilities while accounting for local terrain and obstructions, refer to the Simulate GNSS Multipath Effects on UAV Flying in Urban Environment and Simulate GNSS Multipath Effects in Urban Canyon Environment examples.

Next, using your simulation parameters, generate the satellite visibilities as a matrix of logical values. Each row in the matrix corresponds to a time step, and each column corresponds to a satellite. To plot visibilities, iterate through the time vector while calculating the satellite positions and look angles based on the GNSS constellation simulation.

secondsPerHour = 3600;
timeElapsed = 0:dt:(secondsPerHour*numHours);
t = startTime+seconds(timeElapsed);

numSats = numel(satIDs);
numSamples = numel(t);
az = zeros(numSamples,numSats);
el = zeros(numSamples,numSats);
vis = false(numSamples,numSats);

sp = skyplot([],[],MaskElevation=maskAngle);

for ii = 1:numel(t)
    satPos = gnssconstellation(t(ii),navmsg,GNSSFileType=gnssFileType);
    [az(ii,:),el(ii,:),vis(ii,:)] = lookangles(recPos,satPos,maskAngle);
    set(sp,AzimuthData=az(ii,vis(ii,:)), ...
        ElevationData=el(ii,vis(ii,:)), ...
        LabelData=satIDs(vis(ii,:)))
    drawnow limitrate
end

Plot Results

Use the logical matrix to generate a satellite visibility chart, and plot the total number of visible satellites at each time step. In general, at least four satellites must be visible to compute a positioning solution.

visPlotData = double(vis);
visPlotData(visPlotData == false) = NaN; % Hide invisible satellites.
visPlotData = visPlotData + (0:numSats-1); % Add space to satellites to be stacked.
colors = colororder;

figure
plot(t,visPlotData,".",Color=colors(1,:))
yticks(1:numSats)
yticklabels(string(satIDs))
grid on
ylabel("Satellite ID")
xlabel("Time")
title("Satellite Visibility Chart")
axis tight

numVis = sum(vis,2);
figure
area(t,numVis)
grid on
xlabel("Time")
ylabel("Number of satellites visible")
title("Number of GNSS satellites visible")
axis tight

Multi-Constellation Visibilities

Some GNSS receivers use multiple satellite systems to compute the position solution. To determine the visibility of multiple satellite systems, use the startTime, recPos, and maskAngle simulation parameters and a RINEX navigation message file for each of the GNSS satellite systems.

allSatSys = ["GPS","Galileo","GLONASS","BeiDou","NavIC","QZSS","SBAS"];
satLetter = ["G","E","R","C","I","J","S"];

sp = skyplot([],[],MaskElevation=maskAngle);

for ii = 1:numel(allSatSys)
    satSys = allSatSys(ii);

    % Get the satellite positions of the current satellite system.
    [navmsg,satIDs] = exampleHelperParseGNSSFile("RINEX",satSys);
    satPos = gnssconstellation(startTime,navmsg);
    [az,el,vis] = lookangles(recPos,satPos,maskAngle);
    
    % Combine the satellite system symbol letter with the satellite ID.
    satIDLabel = arrayfun(@(x) sprintf("%c%02d",satLetter(ii),x),satIDs);
    
    % Create a categorical array to associate the current values with the
    % current satellite system in the skyplot.
    satGroup = categorical(repmat(ii,numel(satIDLabel),1),1:numel(allSatSys),allSatSys);
    
    % Update the skyplot.
    set(sp, ...
        AzimuthData=[sp.AzimuthData(:); az(vis)], ...
        ElevationData=[sp.ElevationData(:); el(vis)], ...
        LabelData=[sp.LabelData(:); satIDLabel(vis)], ...
        GroupData=[sp.GroupData; satGroup(vis)]);
end

% Add a legend to the skyplot.
legend

Helper Function

Use the exampleHelperParseGNSSFile helper function to obtain the initial satellite orbital parameters and satellite IDs from the GNSS file, based on the file type.

function [navmsg,satIDs] = exampleHelperParseGNSSFile(gnssFileType,satSys)
    switch gnssFileType
        case "RINEX"
            switch satSys
                case "GPS"
                    file = "GODS00USA_R_20211750000_01D_GN.rnx";
                    navmsg = rinexread(file);
                    % For RINEX files, extract GPS data and use only the first entry for each satellite.
                    gpsData = navmsg.GPS;
                    [~,idx] = unique(gpsData.SatelliteID);
                    navmsg = gpsData(idx,:);
                case "Galileo"
                    file = "GODS00USA_R_20211750000_01D_EN.rnx";
                    navmsg = rinexread(file);
                    % For RINEX files, extract Galileo data and use only the first entry for each satellite.
                    galData = navmsg.Galileo;
                    [~,idx] = unique(galData.SatelliteID);
                    navmsg = galData(idx,:);
                case "GLONASS"
                    file = "GODS00USA_R_20211750000_01D_RN.rnx";
                    navmsg = rinexread(file);
                    % For RINEX files, extract GLONASS data and use only the first entry for each satellite.
                    gloData = navmsg.GLONASS;
                    [~,idx] = unique(gloData.SatelliteID);
                    navmsg = gloData(idx,:);
                case "BeiDou"
                    file = "GODS00USA_R_20211750000_01D_CN.rnx";
                    navmsg = rinexread(file);
                    % For RINEX files, extract BeiDou data and use only the first entry for each satellite.
                    bdsData = navmsg.BeiDou;
                    [~,idx] = unique(bdsData.SatelliteID);
                    navmsg = bdsData(idx,:);
                case "NavIC"
                    file = "ARHT00ATA_R_20211750000_01D_IN.rnx";
                    navmsg = rinexread(file);
                    % For RINEX files, extract NavIC data and use only the first entry for each satellite.
                    irnData = navmsg.NavIC;
                    [~,idx] = unique(irnData.SatelliteID);
                    navmsg = irnData(idx,:);
                case "QZSS"
                    file = "ARHT00ATA_R_20211750000_01D_JN.rnx";
                    navmsg = rinexread(file);
                    % For RINEX files, extract QZSS data and use only the first entry for each satellite.
                    qzsData = navmsg.QZSS;
                    [~,idx] = unique(qzsData.SatelliteID);
                    navmsg = qzsData(idx,:);
                case "SBAS"
                    file = "GOP600CZE_R_20211750000_01D_SN.rnx";
                    navmsg = rinexread(file);
                    % For RINEX files, extract SBAS data and use only the first entry for each satellite.
                    sbaData = navmsg.SBAS;
                    [~,idx] = unique(sbaData.SatelliteID);
                    navmsg = sbaData(idx,:);
            end
            satIDs = navmsg.SatelliteID;
        case "SEM"
            file = "semalmanac_2021-6-22.al3";
            navmsg = semread(file);
            satIDs = navmsg.PRNNumber;
        case "YUMA"
            file = "yumaalmanac_2021-6-22.alm";
            navmsg = yumaread(file);
            satIDs = navmsg.PRN;
    end
end