Spaces:
Running
Running
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)): | |
# TODO: update to below when pyodide matplotlib version>=3.7.2 | |
# t = Affine2D().rotate_deg(angle) | |
# marker = MarkerStyle('_', transform=t) | |
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) | |
# heading_input = pn.widgets.IntSlider(name='Satellite Heading', start=0, end=360, step=1, value=360 - 12, **opts) | |
# grazing_input = pn.widgets.IntSlider(name='Grazing Angle', start=0, end=90, step=1, 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() | |