los_palette / app.py
forrestfwilliams's picture
Upload 2 files
7e896b5
raw
history blame
8.2 kB
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()