|
import panel as pn |
|
import numpy as np |
|
|
|
from matplotlib.figure import Figure |
|
from matplotlib.colors import LinearSegmentedColormap |
|
from matplotlib.markers import MarkerStyle |
|
|
|
from los_palette import angles_to_unit_vector, unit_vector_to_hex |
|
|
|
WIDTH = 800 |
|
BG_COLOR = '#646464' |
|
|
|
|
|
def get_heading_line(vector): |
|
if vector[0] == 0 and vector[1] == 0: |
|
return [0, 0], [0, 0] |
|
|
|
projected_vector = vector.copy() |
|
projected_vector[2] = 0 |
|
unit_vector = (projected_vector / np.linalg.norm(projected_vector)).round(5) |
|
x = [0, unit_vector[0]] |
|
y = [0, unit_vector[1]] |
|
return x, y |
|
|
|
|
|
def get_azimuth_line(vector, left_looking=True): |
|
if vector[0] == 0 and vector[1] == 0: |
|
return [0, 0], [0, 0] |
|
|
|
projected_vector = vector.copy() |
|
projected_vector[2] = 0 |
|
look_offset = 90 if left_looking else -90 |
|
angle_rad = np.deg2rad(-look_offset) |
|
rotation_matrix = np.array( |
|
[[np.cos(angle_rad), -np.sin(angle_rad), 0], [np.sin(angle_rad), np.cos(angle_rad), 0], [0, 0, 1]] |
|
) |
|
rotated_vector = np.dot(rotation_matrix, projected_vector) |
|
unit_vector = (rotated_vector / np.linalg.norm(projected_vector)).round(5) |
|
half_vector = unit_vector * 0.5 |
|
x = [0, half_vector[0]] |
|
y = [0, half_vector[1]] |
|
return x, y |
|
|
|
|
|
def satellite_marker(axis, angle=0, center=(0, 0)): |
|
|
|
|
|
|
|
marker = MarkerStyle('_') |
|
marker._transform.rotate_deg(angle) |
|
|
|
axis.scatter(center[0], center[1], marker=marker, s=3000, lw=4, color='black', zorder=1000) |
|
axis.scatter(center[0], center[1], marker=MarkerStyle('o'), s=200, color='black', zorder=1001) |
|
|
|
|
|
def get_params(heading_angle, grazing_angle, look_direction): |
|
left_looking = look_direction == 'Left Looking' |
|
away_vector = angles_to_unit_vector(heading_angle, grazing_angle, left_looking) |
|
towards_vector = away_vector * -1 |
|
away_color = unit_vector_to_hex(away_vector) |
|
towards_color = unit_vector_to_hex(towards_vector) |
|
return away_vector, left_looking, (away_color, towards_color) |
|
|
|
|
|
def plot_look_direction(params): |
|
away_vector, left_looking, (away_color, _) = params |
|
|
|
x, y = get_heading_line(away_vector) |
|
az_x, az_y = get_azimuth_line(away_vector, left_looking) |
|
unit_circle = np.linspace(0, np.pi * 2, 500) |
|
|
|
fig = Figure(figsize=(6, 6)) |
|
ax = fig.subplots() |
|
ax.plot(np.cos(unit_circle), np.sin(unit_circle), linewidth=1, color=BG_COLOR, zorder=2) |
|
ax.plot(x, y, color=away_color, linestyle='--', label='Look Direction', linewidth=3, zorder=3) |
|
ax.plot(az_x, az_y, color='darkgray', linestyle='--', label='Azimuth Direction', linewidth=3, zorder=4) |
|
angle = np.rad2deg(np.arctan2(away_vector[1], away_vector[0])) |
|
satellite_marker(ax, angle) |
|
|
|
ax.set(xlabel=None, ylabel=None, xlim=(-1.1, 1.1), ylim=(-1.1, 1.1), aspect='equal', facecolor=(0, 0, 0, 0)) |
|
ax.spines['left'].set(position='center', color=BG_COLOR, zorder=0) |
|
ax.spines['bottom'].set(position='center', color=BG_COLOR, zorder=1) |
|
ax.spines['top'].set_visible(False) |
|
ax.spines['right'].set_visible(False) |
|
ax.xaxis.set_ticks([-1, 1], labels=[270, 90], zorder=5) |
|
ax.yaxis.set_ticks([-1, 1], labels=[180, 0]) |
|
ax.legend(loc='upper left', fontsize='large', markerscale=1.5, framealpha=0.5) |
|
|
|
fig.patch.set_alpha(0.0) |
|
fig.tight_layout() |
|
mpl_pane = pn.pane.Matplotlib(fig, format='svg', dpi=150, width=WIDTH // 2) |
|
return mpl_pane |
|
|
|
|
|
def get_grazing_line(vector, left_looking, vertical_offset=1): |
|
if vector[2] == 0: |
|
x = np.array([100, 0, -100]) |
|
y = np.array([0, 0, 0]) |
|
elif vector[0] == 0 and vector[1] == 0: |
|
x = np.array([0, 0, 0]) |
|
y = np.array([-1, 0, 1]) |
|
else: |
|
slope = vector[2] / np.sqrt(vector[0] ** 2 + vector[1] ** 2) |
|
y = np.array([-1, 0, 1]) |
|
x = y / slope |
|
|
|
if left_looking: |
|
x *= -1 |
|
|
|
y += vertical_offset |
|
return x, y |
|
|
|
|
|
def plot_grazing_angle(params): |
|
away_vector, left_looking, (away_color, towards_color) = params |
|
x, y = get_grazing_line(away_vector, left_looking) |
|
|
|
fig = Figure(figsize=(6, 6)) |
|
ax = fig.subplots() |
|
ax.plot(x[:2], y[:2], linewidth=3, linestyle='--', color=away_color, label='Away from Satellite', zorder=2) |
|
ax.plot(x[1:], y[1:], linewidth=3, linestyle='--', color=towards_color, label='Towards Satellite', zorder=3) |
|
angle = np.rad2deg(np.arccos(away_vector[2])) |
|
angle = angle if left_looking else angle + (2 * (180 - angle)) |
|
satellite_marker(ax, angle, (0, 1)) |
|
|
|
ax.set(xlabel=None, ylabel=None, xlim=(-1.25, 1.25), ylim=(0, 2.5), aspect='equal', facecolor=(0, 0, 0, 0)) |
|
ax.spines['left'].set(position='center', color=BG_COLOR, zorder=0) |
|
ax.spines['bottom'].set(color=BG_COLOR, zorder=1) |
|
ax.spines['top'].set_visible(False) |
|
ax.spines['right'].set_visible(False) |
|
ax.xaxis.set_ticks([]) |
|
ax.yaxis.set_ticks([]) |
|
ax.legend(loc='upper left', fontsize='large', markerscale=1.5, framealpha=0.5) |
|
|
|
fig.patch.set_alpha(0.0) |
|
fig.tight_layout() |
|
mpl_pane = pn.pane.Matplotlib(fig, format='svg', dpi=150, width=WIDTH // 2) |
|
return mpl_pane |
|
|
|
|
|
def plot_color_gradient(params): |
|
_, _, (away_color, towards_color) = params |
|
custom_cmap = LinearSegmentedColormap.from_list('custom diverging', [towards_color, 'white', away_color], N=256) |
|
|
|
gradient = np.linspace(0, 1, 256) |
|
gradient = np.vstack((gradient, gradient)) |
|
|
|
fontsize = 14 |
|
fig = Figure(figsize=(12, 1.5)) |
|
ax = fig.subplots() |
|
inset = ax.inset_axes([0.05, 0.05, 0.9, 0.5]) |
|
inset.imshow(gradient, aspect='auto', cmap=custom_cmap) |
|
ax.annotate( |
|
f'Towards satellite\n{towards_color}', |
|
xy=[0.05, 0.6], |
|
fontsize=fontsize, |
|
horizontalalignment='left', |
|
verticalalignment='bottom', |
|
) |
|
ax.annotate('#FFFFFF', xy=[0.5, 0.6], fontsize=fontsize, horizontalalignment='center', verticalalignment='bottom') |
|
ax.annotate( |
|
f'Away from satellite\n{away_color}', |
|
fontsize=fontsize, |
|
xy=[0.95, 0.6], |
|
horizontalalignment='right', |
|
verticalalignment='bottom', |
|
) |
|
inset.set_axis_off() |
|
ax.set_axis_off() |
|
ax.set(facecolor=(0, 0, 0, 0)) |
|
|
|
fig.patch.set_alpha(0.0) |
|
fig.tight_layout() |
|
mpl_pane = pn.pane.Matplotlib(fig, format='svg', dpi=150, width=WIDTH) |
|
return mpl_pane |
|
|
|
|
|
def reset_widgets(menu_value): |
|
options = { |
|
's1a': (348, 34, 'Right Looking'), |
|
's1d': (193, 34, 'Right Looking'), |
|
'vert': (0, 0, 'Right Looking'), |
|
'we': (0, 90, 'Right Looking'), |
|
'sn': (90, 90, 'Right Looking'), |
|
} |
|
heading_input.value, grazing_input.value, look_switch.value = options[menu_value] |
|
|
|
|
|
def on_menu_change(event): |
|
selected_option = event.new |
|
reset_widgets(selected_option) |
|
|
|
|
|
opts = dict(align=('end', 'end'), width=int(WIDTH / 4.5)) |
|
heading_input = pn.widgets.IntInput(name='Satellite Heading (0-360)', start=0, end=360, step=5, value=360 - 12, **opts) |
|
grazing_input = pn.widgets.IntInput(name='Grazing Angle (0-90)', start=0, end=90, step=5, value=34, **opts) |
|
|
|
|
|
look_switch = pn.widgets.ToggleGroup(options=['Right Looking', 'Left Looking'], behavior='radio', **opts) |
|
menu_items = [ |
|
('Sentinel-1 Ascending', 's1a'), |
|
('Sentinel-1 Descending', 's1d'), |
|
('Vertical', 'vert'), |
|
('West-East', 'we'), |
|
('South-North', 'sn'), |
|
] |
|
menu = pn.widgets.MenuButton(name='Presets', items=menu_items, button_type='primary', **opts) |
|
menu.on_click(on_menu_change) |
|
|
|
params = pn.bind(get_params, heading_input, grazing_input, look_switch) |
|
interactive_look = pn.bind(plot_look_direction, params) |
|
interactive_grazing = pn.bind(plot_grazing_angle, params) |
|
interactive_color = pn.bind(plot_color_gradient, params) |
|
pn.Column( |
|
pn.Row(menu, heading_input, grazing_input, look_switch, height=100), |
|
pn.Row(interactive_look, interactive_grazing), |
|
pn.Row(interactive_color), |
|
).servable() |
|
|