Main Content

Simulating Radar Returns from Moving Sea Surfaces

Maritime radar systems operate in a challenging, dynamic environment. To improve detection of targets of interest and assess system performance, it is important to understand the nature of returns from sea surfaces.

In this example, you will simulate an X-band radar system used for oceanographic studies of sea states. The radar system is a fixed, offshore platform. You will first generate a moving sea surface at sea state 4 using the Elfouhaily spectrum model. You will then generate IQ returns from the sea surface and investigate the statistics and time-frequency behavior of the simulated sea surface signals.

Define Radar System Parameters

Define an X-band radar system with a range resolution approximately equal to 30 meters. Verify the range resolution is as expected using the bw2rangeres function.

rng(2021)       % Initialize random number generator

% Radar parameters
freq = 9.39e9;  % Center frequency (Hz)
prf = 500;      % PRF (Hz)
tpd = 100e-6;   % Pulse width (s)
azBw = 2;       % Azimuth beamwidth (deg)
depang = 30;    % Depression angle (deg)
antGain = 45.7; % Antenna gain (dB)
fs = 10e6;      % Sampling frequency (Hz)
bw = 5e6;       % Waveform bandwidth (Hz)
ans = 29.9792

Next, define the sea surface as a sea state 4 with a wind speed of 19 knots and a resolution of 2 meters. Set the length of the sea surface to 512 meters.

% Sea surface
seaState = 4;   % Sea state number 
vw = 19;        % Wind speed (knots)
L = 512;        % Sea surface length (m) 
resSurface = 2; % Resolution of sea surface (m)

% Calculate wavelength and get speed of light
[lambda,c] = freq2wavelen(freq);

% Setup sensor trajectory and simulation times
rdrht = 100;                                   % Radar platform height (m) 
rdrpos = [-L/2 0 rdrht];                       % Radar position [x y z] (m)
numPulses = 1024;                              % Number of pulses 
scenarioUpdateTime = 1/prf;                    % Scenario update time (sec) 
scenarioUpdateRate = prf;                      % Scenario update rate (Hz)
simTime = scenarioUpdateTime.*(numPulses - 1); % Overall simulation time (sec) 

Model the Sea Surface

Define a scenario with radarScenario. Next, add sea motion with seaSpectrum. Keep the SpectrumSource property default Auto to use an Elfouhaily spectrum. Set the resolution to 2 meters. The Elfouhaily model consists of a sea spectrum and an angular spreading function. The sea spectrum model gets physical properties like wind speed and fetch from the seaSurface, as you will see later in this section.

You can also import your own custom model by setting the seaSpectrum SpectrumSource property to Custom. Alternative sea spectrum models in the literature include the JONSWAP, Donelan-Pierson, and Pierson-Moskovitz spectrums.

% Create scenario
scene = radarScenario('UpdateRate',scenarioUpdateRate, ...

% Define Elfouhaily sea spectrum 
seaSpec = seaSpectrum('Resolution',resSurface) % See Elfouhaily reference
seaSpec = 
  seaSpectrum with properties:

    SpectrumSource: 'Auto'
        Resolution: 2

Now, select a reflectivity model. Radar Toolbox™ offers 9 different reflectivity models for sea surfaces covering a wide range of frequencies, grazing angles, and sea states. The asterisk denotes the default model. Type help surfaceReflectivitySea or doc surfaceReflectivitySea in the Command Window for more information on usage and applicable grazing angles for each model. The sea reflectivity models are as follows.

  • APL: Semi-empirical model supporting low grazing angles over frequencies in the range from 1 to 100 GHz. Applicable for sea states 1 through 6.

  • GIT: Semi-empirical model supporting low grazing angles over frequencies in the range from 1 to 100 GHz. Applicable for sea states 1 through 6.

  • Hybrid: Semi-empirical model supporting low to medium grazing angles over frequencies in the range from 500 MHz to 35 GHz. Applicable for sea states 0 through 5.

  • Masuko: Empirical model supporting low grazing angles over frequencies in the range from 8 to 40 GHz. Applicable for sea states 1 through 6.

  • Nathanson: Empirical model supporting low grazing angles over frequencies in the range from 300 MHz to 36 GHz. Applicable for sea states 0 through 6.

  • NRL*: Empirical model supporting low grazing angles over frequencies in the range from 500 MHz to 35 GHz. Applicable for sea states 0 through 6.

  • RRE: Mathematical model supporting low grazing angles over frequencies in the range from 9 to 10 GHz. Applicable for sea states 1 through 6.

  • Sittrop: Empirical model supporting low grazing angles over frequencies in the range from 8 to 12 GHz. Applicable for sea states 0 through 7.

  • TSC: Empirical model supporting low grazing angles over frequencies in the range from 500 MHz to 35 GHz. Applicable for sea states 0 through 5.

For this example, set the reflectivity to the GIT (Georgia Institute of Technology) model. For a general discussion about sea reflectivity models and how they are affected by frequency, grazing angle, sea state, and polarization, see the Maritime Radar Sea Clutter Modeling example.

% Define reflectivity model
pol = 'V'; % Polarization 
reflectModel = surfaceReflectivity('Sea','Model','GIT','SeaState',seaState,'Polarization',pol)
reflectModel = 
  surfaceReflectivitySea with properties:

    EnablePolarization: 0
                 Model: 'GIT'
              SeaState: 4
          Polarization: 'V'
               Speckle: 'None'

Add a sea surface to the radar scenario using seaSurface. Assign the previously defined sea spectrum and reflectivity model to the sea surface. seaSurface houses the physical properties of the surface. The WindSpeed, WindDirection, and Fetch properties are used in generating the Elfouhaily sea spectrum. By thoughtful selection of these properties and the resolution of the sea surface, gravity and capillary waves with different wave ages can be produced.

Capillary waves are high-frequency, small-wavelength waves that have a rounded crest with a V-shaped trough that are less than 2 cm. Gravity waves are longer, low-frequency waves. The seaSpectrum object cannot model breaking waves, which are waves that reach a critical amplitude resulting in wave energy being transformed into turbulent, kinetic energy. The greater the wind speed, the greater the energy transferred to the sea surface and the larger the waves.

Fetch is the length of water over which wind blows without obstruction. The Elfouhaily model relates the fetch to the inverse wave age ΩC as


X is calculated as

k0=gU10 and X=k0Fetch,

where g is the acceleration due to gravity in meters per second squared, U10 is the wind speed at 10 meters above the surface in m/s, and X0is a constant set to 2.2e4. A fully-developed sea has an inverse wave age of 0.84, a mature sea has a value of approximately 1, and a young sea has a value between 2 and 5.

The default fetch for seaSurface is infinity, which produces a fully-developed sea. More information about the Elfouhaily spectrum can be found in the reference listed at the end of this example, as well as in the documentation for seaSpectrum.

The elevation values of the sea surface can be obtained by using the height method on the sea surface. Plot the radar and the sea surface using helperSeaSurfacePlot.

% Configure sea surface
knots2mps = 0.514444; % Knots to meters/sec
vw = vw*knots2mps; % Wind speed (m/s)
seaSurf = seaSurface(scene,'SpectralModel',seaSpec,'RadarReflectivity',reflectModel, ...
    'WindSpeed',vw,'WindDirection',0,'Boundary',[-L/2 L/2; -L/2 L/2])
seaSurf = 
  SeaSurface with properties:

                WindSpeed: 9.7744
            WindDirection: 0
                    Fetch: Inf
            SpectralModel: [1x1 seaSpectrum]
        RadarReflectivity: [1x1 surfaceReflectivitySea]
    ReflectionCoefficient: [1x1 radar.scenario.SurfaceReflectionCoefficient]
          ReflectivityMap: 1
          ReferenceHeight: 0
                 Boundary: [2x2 double]

% Plot sea surface and radar 
x = -L/2:resSurface:(L/2 - 1); 
y = -L/2:resSurface:(L/2 - 1); 
[xGrid,yGrid] = meshgrid(x,y);
z = height(seaSurf,[xGrid(:).'; yGrid(:).'],scene.SimulationTime); 

Investigate the statistics of the elevation values of the sea surface. First, calculate and plot the cumulative distribution function using helperSeaSurfaceCDF. You can use this plot to determine the probability that an observation will be less than or equal to a certain elevation value. For example, 90% of elevations will be less than or equal to about 1 meter. 95% of elevations will be less than or equal to about 1.5 meters. If you would like to alter the statistics of the generated sea surface to increase the elevations, you can increase the wind speed to deliver more energy to the surface.


Next, estimate the significant wave height using helperEstimateSignificantWaveHeight. The estimate is obtained by taking the mean of the highest 1/3 of the wave heights, where the wave height is defined from trough-to-crest as is visually represented in the figure.

% Significant wave height
actSigHgt = helperEstimateSignificantWaveHeight(x,y,z)

actSigHgt = 1.6340

The expected wave heights for a sea state 4 is in the range between 1.25 and 2.5 meters. Note that the simulated sea surface is within the range of expected values.

expectedSigHgt = [1.25 2.5]; % Sea state 4
actSigHgt >= expectedSigHgt(1) && actSigHgt <= expectedSigHgt(2)
ans = logical

Lastly, plot a subset of the sea surface heights over time to visualize the motion due to the Elfouhaily sea spectrum. Note that the simulation time for the radar data collection only runs for 2 seconds, but plot over a time period of 10 seconds to get a better sense of the motion. Uncomment helperSeaSurfaceMotionPlot to plot the moving sea surface.

% Plot sea surface motion 
plotTime = 0:0.5:10; % Plot time (sec)
% helperSeaSurfaceMotionPlot(x,y,seaSurf,plotTime); % Uncomment to plot motion


Configure the Radar Transceiver

In this section, configure the radar system properties. Define the antenna and the transmitted linear frequency modulated (LFM) waveform. Assign the radar sensor to the radar platform.

% Create a radar platform using the trajectory information
rdrplat = platform(scene,'Position',rdrpos);

% Create a radar sensor looking to the East
rdr = radarTransceiver('MountingAngles',[0 depang 0],'NumRepetitions',1);

% Configure the LFM signal of the radar
rdr.Waveform = phased.LinearFMWaveform('SampleRate',fs,'PulseWidth',tpd, ...

% Set receiver sample rate and noise figure
rdr.Receiver.SampleRate = fs;
rdr.Receiver.NoiseFigure = 10; 

% Define transmit and receive antenna and corresponding parameters
ant = phased.SincAntennaElement('Beamwidth',azBw);
rdr.TransmitAntenna.OperatingFrequency = freq;
rdr.ReceiveAntenna.OperatingFrequency = freq;
rdr.Transmitter.Gain = antGain;
rdr.Receiver.Gain = antGain;
rdr.TransmitAntenna.Sensor = ant;
rdr.ReceiveAntenna.Sensor = ant;

% Add radar to radar platform
rdrplat.Sensors = rdr;

Generate the Datacube

Now that the scene and the radar system have been defined, generate returns from the sea surface with the clutterGenerator method and collect the IQ data using the receive method, which both reside in the radarScenario object. By default, clutterGenerator simulates clutter returns in the mainlobe. For more information about clutter modeling, see Introduction to Radar Scenario Clutter Simulation.

Use the helpers helperPlotRawIQ and helperUpdatePlotRawIQ to plot the unprocessed, raw IQ data.

% Collect clutter returns with the clutterGenerator

% Run the scenario
numSamples = 1/prf*fs;
maxRange = 20e3; 
Trng = (0:1/fs:(numSamples-1)/fs);
rngGrid = [0 time2range(Trng(2:end),c)];
[~,idxTruncate] = min(abs(rngGrid - maxRange));
iqsig = zeros(idxTruncate,numPulses);
ii = 1; 
hRaw = helperPlotRawIQ(iqsig);
while advance(scene)
    tmp = receive(scene); % nsamp x 1
    iqsig(:,ii) = tmp{1}(1:idxTruncate); 
    if mod(ii,128) == 0
    ii = ii + 1;

Process Sea Surface Returns

Use phased.RangeResponse to pulse compress the returned IQ data. Visualize the range-time behavior using helperRangeTimePlot. If the sea surface was static, you would see straight, horizontal lines in the plot, but the sea returns exhibit dynamism. At a given range, the magnitude changes with time. Note that the returns occur in a small subset of range due to the geometry (radar height, beamwidth, and depression angle).

% Pulse compress
matchingCoeff = getMatchedFilter(rdr.Waveform);
rngresp = phased.RangeResponse('RangeMethod', 'Matched filter', ...
[pcResp,rngGrid] = rngresp(iqsig,matchingCoeff); 

% Plot
pcsigMagdB = mag2db(abs(pcResp));
[maxVal,maxIdx] = max(pcsigMagdB(:));
pcsigMagdB = pcsigMagdB - maxVal;

Use the range index of the maximum value and visualize the magnitude versus time at this range bin using helperMagTimePlot.

% Plot magnitude versus time
[idxRange,~] = ind2sub(size(pcsigMagdB),maxIdx); 

Next, generate a time-frequency plot of that same range bin using helperSTFTPlot. Generate the plot using the short-time Fourier transform function stft.

[S,F,T] = stft(pcResp(idxRange,:),scenarioUpdateRate);

Notice that the detected speed of the range bin changes over time due to the motion of the sea surface. Over longer simulation times, periodicities are detectable.

Lastly, form a histogram from the magnitude values of the data in the ranges between 180 and 210 meters using helperHistPlot. Note the shape of the histogram resembles a Weibull distribution. If you have the Statistics and Machine Learning Toolbox™, you can use the fitdist function to get the parameters for the Weibull distribution from the magnitude of the data.

% Look at a subset of data in range and convert to decibel scale
[~,idxMin] = min(abs(rngGrid - 180)); 
[~,idxMax] = min(abs(rngGrid - 210)); 
pcsigMagdB = mag2db(abs(pcResp(idxMin:idxMax,:)));

% Remove any inf values
pcsigMagdB(isinf(pcsigMagdB(:))) = []; 

% Shift values to be positive
pcsigMagdB = pcsigMagdB(:) - min(pcsigMagdB(:)) + eps; 

% Weibull parameters
% Note: These values can be obtained if you use fitdist(pcsigMagdB,'weibull')
scale = 32.5013;
shape = 6.3313;

% Plot histogram with theoretical PDF overlay


In this example, you learned how to:

  • Build a radar scenario,

  • Model a moving sea surface,

  • Investigate sea surface statistics and behavior,

  • Generate IQ, and

  • Perform time-frequency processing on simulated IQ returns.

This example can easily be extended to include targets to support applications ranging such as maritime surveillance and radar imaging.


  1. Apel, John R. Principles of Ocean Physics. International Geophysics Series, v. 38. London; Orlando: Academic Press, 1987.

  2. Elfouhaily, T., B. Chapron, K. Katsaros, and D. Vandemark. "A Unified Directional Spectrum for Long and Short Wind-Driven Waves." Journal of Geophysical Research: Oceans 102, no. C7 (July 15, 1997): 15781-96.

  3. Doerry, Armin W. "Ship Dynamics for Maritime ISAR Imaging." Sandia Report. Albuquerque, New Mexico: Sandia National Laboratories, February 2008.

  4. Ward, Keith D., Simon Watts, and Robert J. A. Tough. Sea Clutter: Scattering, the K-Distribution and Radar Performance. IET Radar, Sonar, Navigation and Avionics Series 20. London: Institution of Engineering and Technology, 2006.

Supporting Functions


function cmap = helperSeaColorMap(n)
% Color map for sea elevation plot

c = hsv2rgb([2/3 1 0.2; 2/3 1 1; 0.5 1 1]);
cmap = zeros(n,3);
cmap(:,1) = interp1(1:3,c(:,1),linspace(1,3,n)); 
cmap(:,2) = interp1(1:3,c(:,2),linspace(1,3,n));
cmap(:,3) = interp1(1:3,c(:,3),linspace(1,3,n)); 


function helperSeaSurfacePlot(x,y,z,rdrpos)
% Plot sea elevations

% Color map for sea elevation plot
seaColorMap = helperSeaColorMap(256);

% Plot
z = reshape(z,numel(x),numel(y)); 
hold on
legend('Sea Surface','Radar','Location','Best')
shading interp
axis equal
xlabel('X (m)')
ylabel('Y (m)')
zlabel('Elevations (m)')
stdZ = std(z(:));
minC = -4*stdZ;
maxC = 4*stdZ;
minZ = min(minC,rdrpos(3)); 
maxZ = max(maxC,rdrpos(3)); 
title('Sea Surface Elevations')
axis tight
zlim([minZ maxZ])
hC = colorbar('southoutside');
hC.Label.String = 'Elevations (m)';
hC.Limits = [minC maxC];


function helperSeaSurfaceCDF(x)
% Calculate and plot CDF 

% Form CDF
x = x(~isnan(x));
n = length(x);
x = sort(x(:));
yy = (1:n)' / n;
notdup = ([diff(x(:)); 1] > 0);
xx = x(notdup);
yy = [0; yy(notdup)];

% Create vectors for plotting
k = length(xx);
n = reshape(repmat(1:k, 2, 1),2*k,1);
xCDF = [-Inf; xx(n); Inf];
yCDF = [0; 0; yy(1+n)];

% Plot
grid on
title('Wave Elevation CDF') 
xlabel('Wave Elevation (m)')


function sigHgt = helperEstimateSignificantWaveHeight(x,y,z)
% Plot an example showing how the wave heights are estimated and estimate
% the significant wave height 

% Plot wave height estimation
numX = numel(x);
z = reshape(z,numX,numel(y));
zEst = z(numX/2 + 1,:);
grid on
hold on
xlabel('X (m)')
ylabel('Elevation (m)')
title('Wave Height Estimation')
axis tight
idxMin = islocalmin(z(numel(x)/2 + 1,:));
idxMax = islocalmax(z(numel(x)/2 + 1,:));
co = colororder; 
plot(x(idxMin),zEst(idxMin),'v', ...
plot(x(idxMax),zEst(idxMax),'^', ...
legend('Wave Elevation Data','Trough','Crest','Location','SouthWest')

% Estimate significant wave height
waveHgts = [];
for ii = 1:numX
    zEst = z(ii,:);
    idxMin = islocalmin(zEst);
    troughs = zEst(idxMin);
    numTroughs = sum(double(idxMin)); 
    idxMax = islocalmax(zEst);
    crests = zEst(idxMax);
    numCrests = sum(double(idxMax)); 
    numWaves = min(numTroughs,numCrests); 
    waveHgts = [waveHgts ...
        abs(crests(1:numWaves) - troughs(1:numWaves))]; %#ok<AGROW> 
waveHgts = sort(waveHgts); 
idxTopThird = floor(numel(waveHgts)*2/3);
sigHgt = mean(waveHgts(idxTopThird:end));


function helperSeaSurfaceMotionPlot(x,y,seaSurf,plotTime) %#ok<DEFNU> 
% Color map for sea elevation plot
seaColorMap = helperSeaColorMap(256);

% Get initial height 
[xGrid,yGrid] = meshgrid(x,y);
z = height(seaSurf,[xGrid(:).'; yGrid(:).'],plotTime(1)); 

% Plot
hFig = figure;
hS = surf(x,y,reshape(z,numel(x),numel(y)));
hold on
shading interp
axis equal
xlabel('X (m)')
ylabel('Y (m)')
zlabel('Elevations (m)')
stdZ = std(z(:));
minZ = -4*stdZ; 
maxZ = 4*stdZ; 
title('Sea Surface Motion Plot')
xlim([-50 50])
ylim([-50 50])
zlim([minZ maxZ])
hC = colorbar('southoutside');
hC.Label.String = 'Elevations (m)';
hC.Limits = [minZ maxZ];
view([-45 12])

% Change figure size
ppos0 = get(hFig,'Position');
figWidth = 700;
figHeight = 300;

numTimes = numel(plotTime);
for ii = 2:numTimes
    % Update plot
    z = height(seaSurf,[xGrid(:).'; yGrid(:).'],plotTime(ii));
    hS.ZData = reshape(z,numel(x),numel(y));


function hRaw = helperPlotRawIQ(iqsig)
% Plot raw IQ magnitude

hRaw = pcolor(mag2db(abs(iqsig)));
hRaw.EdgeColor = 'none';
title('Raw IQ')
hC = colorbar;
hC.Label.String = 'Magnitude (dB)'; 


function helperUpdatePlotRawIQ(hRaw,iqsig)
% Update the raw IQ magnitude plot

hRaw.CData = mag2db(abs(iqsig)); 


function helperRangeTimePlot(rngGrid,prf,pcsigMagdB)
% Range-Time Plot

numPulses = size(pcsigMagdB,2); 
hImage = pcolor((1:numPulses)*1/prf,rngGrid,pcsigMagdB);
hImage.EdgeColor = 'none';
shading interp
xlabel('Time (sec)')
ylabel('Range (m)')
hC = colorbar;
hC.Label.String = 'Magnitude (dB)'; 
axis tight
title('Range versus Time')
clim([-20 0])
ylim([140 260])


function helperMagTimePlot(magVals,numPulses,prf,rngVal)
% Magnitude vs. Time Plot

grid on
xlabel('Time (sec)')
ylabel('Magnitude (dB)')
axis tight
title(sprintf('Magnitude versus Time at Range %.2f (m)',rngVal))
ylim([-20 0])


function helperSTFTPlot(S,F,T,lambda,rngVal)
% Time-Frequency Plot 

S = mag2db(abs(S)); % Convert to dB
S = S - max(S(:)); % Normalize
Speed = dop2speed(F,lambda)/2; % Speed (m/s)
hSTFT = pcolor(T,Speed,S);
hold on
hSTFT.EdgeColor = 'none';
xlabel('Time (sec)')
ylabel('Speed (m/s)')
title(sprintf('STFT at Range %.2f (m)',rngVal))
clim([-20 0])
ylim([-1 1])


function helperHistPlot(pcsigMagdB,scale,shape)
% Amplitude Distribution

% Histogram
hHist = histogram(pcsigMagdB,'Normalization','pdf'); % Create histogram
grid on
hold on
xlabel('Normalized Magnitude (dB)')
title('Amplitude Histogram')

% Lognormal overlay
edges = hHist.BinEdges;
x = (edges(2:end) + edges(1:end-1))./2;
z = x./scale;
w = exp(-(z.^shape));
y = z.^(shape - 1).*w .*shape./scale;