function plot(pd,varargin)
%plot   Display RF propagation data in Site Viewer
%   plot(PD) displays propagation data in the current Site Viewer. Each
%   data point is plotted as a circular marker that is colored according to
%   the corresponding data value.
%
%   plot(___,Name,Value) displays propagation data with additional options
%   specified by one or more Name-Value pairs.
%
%   plot Name-Value pairs:
%
%   DataVariableName - Data variable to plot, specified as a character
%      vector or string scalar corresponding to a variable name in the Data
%      table of PD. The default value is the DataVariableName property
%      value of PD.
%
%   Type - Type of value to plot, specified as 'custom' (the default
%      value), 'power', 'efield', 'sinr', or 'pathloss'. The choice of Type
%      determines default values for Levels, ColorLimits, and LegendTitle.
%      The default value for Levels is a linearly spaced vector bounded by
%      ColorLimits, and default values for ColorLimits and LegendTitle are
%      set as shown:
%      
%         Type       | ColorLimits           | LegendTitle
%         -------------------------------------------------------
%         'custom'   | [min(Data) max(Data)] | ''
%         'power'    | [-120 -5]             | 'Power (dBm)'
%         'efield'   | [20 135]              | 'E-field (dBuV/m)'
%         'sinr'     | [-5 20]               | 'SINR (dB)'
%         'pathloss' | [45 160]              | 'Path loss (dB)'
%
%   Levels - Data value levels to plot, specified as a numeric vector. The
%      propagation data is binned according to Levels, and data in each bin
%      is color-coded according to the corresponding level. The colors are
%      selected using Colors if specified or else Colormap and ColorLimits.
%      Data points with values below the minimum level are not included in
%      the plot.
%
%   Colors - Colors of the data points, specified as an M-by-3 array of RGB
%      (red, green, blue) triplets or array of strings or cell array of
%      character vectors. Colors are assigned element-wise to values in
%      Levels for coloring the corresponding points. Colors may not be used
%      with Colormap or ColorLimits.
%
%   ColorLimits - Color limits for the colormap, specified as a two-element
%      vector of the form [min max]. The color limits indicate the data
%      level values that map to the first and last colors in the colormap.
%      ColorLimits may not be used with Colors.
%
%   Colormap - Colormap for coloring the points, specified as a predefined
%      colormap name or an M-by-3 array of RGB (red, green, blue) triplets
%      that define M individual colors. The default value is 'jet'.
%      Colormap may not be used with Colors.
%
%   MarkerSize - Size of the data markers plotted on the map, specified as 
%      a positive numeric scalar in pixels. The default value is 10.
%
%   ShowLegend - Show color legend on map, specified as true or false.
%      The default value is true.
%
%   LegendTitle - Title of the color legend, specified as a string scalar
%      or character vector.
%
%   Map - Map for visualization, specified as a siteviewer object. The
%      default value is the current siteviewer or else a new siteviewer if
%      none is open.
%
%   % Example: Plot computed signal strength data in an urban environment
%
%   % Launch siteviewer with basemap and buildings file
%   viewer = siteviewer("Basemap","streets_dark",...
%       "Buildings","manhattan.osm");
%
%   % Show a transmitter site on a building
%   tx = txsite("Latitude",40.7107,...
%       "Longitude",-74.0114,...
%       "AntennaHeight",80);
%   show(tx)
%
%   % Create receiver sites located along nearby streets
%   latitude = [linspace(40.7088, 40.71416, 50), ...
%       linspace(40.71416, 40.715505, 25), ...
%       linspace(40.715505, 40.7133, 25), ...
%       linspace(40.7133, 40.7143, 25)]';
%   longitude = [linspace(-74.0108, -74.00627, 50), ...
%       linspace(-74.00627 ,-74.0092, 25), ...
%       linspace(-74.0092, -74.0110, 25), ...
%       linspace(-74.0110, -74.0132, 25)]';
%
%   rxs = rxsite("Latitude", latitude, "Longitude", longitude);
%
%   % Compute signal strength at each receiver location
%   signalStrength = sigstrength(rxs,tx)';
%
%   % Create propagationData object to hold computed signal strength data
%   tbl = table(latitude, longitude, signalStrength);
%   pd = propagationData(tbl);
%
%   % Plot signal strength data on the map as colored points
%   legendTitle = "Signal" + newline + "Strength" + newline + "(dB)";
%   plot(pd, "LegendTitle", legendTitle, "Colormap", parula);
%
% See also contour, getDataVariable, interp

%   Copyright 2019-2022 The MathWorks, Inc.

validateattributes(pd,{'propagationData'},{'scalar'},'propagationData/plot','',1);

% Process optional name/value pairs
p = inputParser;
p.addParameter('Animation', '');
p.addParameter('EnableWindowLaunch', true);
p.addParameter('DataVariableName', pd.DataVariableName);
p.addParameter('Type', 'custom');
p.addParameter('Levels', []);
p.addParameter('Colors', []);
p.addParameter('ColorLimits', []);
p.addParameter('Colormap', 'jet');
p.addParameter('MarkerSize', 10);
p.addParameter('ShowLegend', true);
p.addParameter('LegendTitle', '');
p.addParameter('Map', []);
p.parse(varargin{:});

% Get Site Viewer and control parameters
viewer = rfprop.internal.Validators.validateMap(p, 'plot');
isViewerInitiallyVisible = viewer.Visible;
[animation, enableWindowLaunch] = rfprop.internal.Validators.validateGraphicsControls(...
    p, isViewerInitiallyVisible, 'plot');

% Get data variable name and value
if ismember('DataVariableName',p.UsingDefaults)
    dataVariableName = pd.DataVariableName;
else
    dataVariableName = validateDataVariableName(pd, p.Results.DataVariableName, 'plot');
end
[data,lats,lons,alts] = pd.getDataVariable(dataVariableName);

% Validate Type and and get corresponding default parameter values
allowedTypes = {'power','efield','sinr','pathloss','custom'};
[type, levels, defaultColorLimits, defaultLegendTitle] = ...
    rfprop.internal.Validators.validateType(p, allowedTypes, 'plot', data);

% Validate graphical parameters
colors = rfprop.internal.Validators.validateColors(p, 'plot');
clim = rfprop.internal.Validators.validateColorLimits(p, defaultColorLimits, 'plot');
cmap = rfprop.internal.Validators.validateColorMap(p, 'plot');
isPathlossPlot = strcmp(type,'pathloss');
if isPathlossPlot
    % Flip colormap to maintain consistency of color meaning (red end of
    % jet means good signal), since path loss is inversely proportional to
    % received power
    cmap = flipud(cmap);
end
markerSize = validateMarkerSize(p);
showLegend = rfprop.internal.Validators.validateShowLegend(p, 'plot');
legendTitle = rfprop.internal.Validators.validateLegendTitle(p, defaultLegendTitle, 'plot');

% Compute data/legend colors, removing data below the lowest level
cdata = pd.discretizeDataToLevels(data, levels);
isBelowLevels = isnan(cdata);
cdata(isBelowLevels) = [];
data(isBelowLevels) = [];
lats(isBelowLevels) = [];
lons(isBelowLevels) = [];
alts(isBelowLevels) = [];
useColors = ~isempty(colors);
[markerRGB, legendColors, legendColorValues] = rfprop.internal.ColorUtils.dataColors(...
    cdata, useColors, colors, cmap, clim, levels, showLegend);
if isPathlossPlot
    legendColors = fliplr(legendColors);
    legendColorValues = fliplr(legendColorValues);
end

if useColors
    colorData = struct('Levels',levels,'Colors',colors);
else
    colorData = struct('Colormap',cmap,'ColorLimits',clim);
end

% Compute plot locations for data, where elevation (Z) must match terrain
% source height reference
lats = double(lats);
lons = wrapTo180(double(lons));
switch pd.AltitudeReference
    case 'geoid'
        Z = alts;
        if viewer.UseTerrain && strcmpi(viewer.TerrainSource.HeightReference,'ellipsoid')
            Z = terrain.internal.HeightTransformation.orthometricToEllipsoidal(Z, lats, lons);
        end
    case 'ellipsoid'
        Z = alts;
        if viewer.UseTerrain && strcmpi(viewer.TerrainSource.HeightReference,'geoid')
            Z = terrain.internal.HeightTransformation.ellipsoidalToOrthometric(Z, lats, lons);
        end
    case 'ground'
        Z = alts + rfprop.internal.AntennaSiteCoordinates.queryGroundHeightAboveTerrainReference(...
            lats, lons, viewer);
    case 'surface'
        Z = alts + rfprop.internal.AntennaSiteCoordinates.querySurfaceHeightAboveTerrainReference(...
            lats, lons, viewer);
end
plotLocations = [lats lons Z];
pdID = pd.pUID;
% Clear old plot color info for the pd. This is because a propData can
% never conflict with itself (since a propData will replot it's own points if
% plot is called on the same propData multiple times)
% Also mark the legend and old plot graphics to be removed
oldPlotID = viewer.getGraphic(pdID, 'plot');
graphicsToRemove = {};
if (~isempty(oldPlotID))
    viewer.removeColorData(oldPlotID);
    graphicsToRemove = [graphicsToRemove; oldPlotID];
end
if ~isempty(viewer.LegendID)
    graphicsToRemove = [graphicsToRemove; viewer.LegendID];
    viewer.LegendID = '';
    viewer.LegendEntities = strings(0);
end

% Validate the plot against other plots and contours
viewer.checkForGraphicsConflict(type, pd.plotID, colorData);

viewer.remove(graphicsToRemove);

% Add legend
composite = globe.internal.CompositeModel;
if showLegend
    lv = globe.internal.LegendViewer;
    legendID = ['legend' pdID];
    [~, legendDescriptor] = lv.buildPlotDescriptors(legendTitle, legendColors, legendColorValues, "ID", legendID);
    viewer.LegendID = legendID;
    composite.addGraphic("colorLegend", legendDescriptor);
end

% Compute descriptions
descriptionTitle = pd.Name;
numData = numel(data);
descriptions = cell(numData,1);
dec = rfprop.Constants.MaxLatLonInfoDecimalPlaces;
for k=1:numData
    descriptions{k} = [mat2str(round(lats(k), dec)) ', ' mat2str(round(lons(k), dec)) ...
        '<br />' dataVariableName ': ' mat2str(round(data(k), dec))];
end

% Add point markers
[~, pointDescriptor] = viewer.Instance.GlobeViewer.getViewer('Point').buildPlotDescriptors(plotLocations, ...
    'Color', num2cell(markerRGB(:,:),2), ...
    'Size', markerSize, ...
    'Animation', animation, ...
    'Name', descriptionTitle, ...
    'ID', {pd.plotID}, ...
    'Description', descriptions, ...
    'EnableWindowLaunch', enableWindowLaunch);
composite.addGraphic('point',pointDescriptor);
% Set the sync flag to true for the point plot to tell the JS code that
% when it clears the graphic, it can avoid manual clearing of entities to
% work around a 3p performance bug
composite.PlotDescriptors{end}.WaitForResponse = true;
compositeController = globe.internal.CompositeController(viewer.Instance.GlobeViewer.Controller);

% Zoom if the viewer isn't open yet
compositeArgs = composite.buildPlotDescriptors;
if ~isViewerInitiallyVisible && (isempty(animation) || strcmp(animation, 'zoom'))
    compositeArgs.Animation = 'zoom';
end

% Plot composite
compositeController.composite(compositeArgs)
viewer.SiteGraphics.(pdID).plot = pd.plotID;

% Show the viewer and bring it into focus
if ~isViewerInitiallyVisible && enableWindowLaunch
    viewer.Visible = true;
end
viewer.bringToFront;
end

function markerSize = validateMarkerSize(p)

try
    markerSize = p.Results.MarkerSize;
    validateattributes(markerSize, {'numeric'}, ...
        {'real','nonsparse','finite','scalar','positive','integer','<=',1000}, 'plot', 'MarkerSize');
catch e
    throwAsCaller(e);
end
end
