Add saving of annotations to the interactive figure. Also added the option of specifying the center of a circle annotation in the constructor. That way we do not have to make a potentially blocking call to the widget's get_center if we are going to change the center immediately anyways.

This commit is contained in:
Jeffrey SubbaRao 2019-05-23 11:11:14 -05:00
Родитель eba8e2b5ca
Коммит cd4583391f
3 изменённых файлов: 124 добавлений и 31 удалений

Просмотреть файл

@ -48,6 +48,7 @@ class Annotation(HasTraits):
super(Annotation, self).__init__(**kwargs)
else:
raise KeyError('a key doesn\'t match any annotation trait name')
self.parent._annotation_set.add(self)
def _on_trait_change(self, changed):
# This method gets called anytime a trait gets changed. Since this class
@ -62,6 +63,26 @@ class Annotation(HasTraits):
setting=wwt_name,
value=changed['new'])
def _serialize_state(self):
state = {'shape': self.shape,
'id': self.id,
'settings': []}
for trait in self.traits().values():
wwt_name = trait.metadata.get('wwt')
if wwt_name:
trait_val = trait.get(self)
if isinstance(trait_val, u.Quantity):
trait_val = trait_val.value
state['settings'].append({'name': wwt_name, 'value': trait_val})
return state
def remove(self):
"""
Removes the specified annotation from the current view.
"""
self.parent._send_msg(event='remove_annotation', id=self.id)
self.parent._annotation_set.discard(self)
class Circle(Annotation):
"""
@ -80,13 +101,20 @@ class Circle(Annotation):
'(`str` or `tuple`)').tag(wwt='fillColor')
line_color = Color('white', help='Assigns line color for the circle '
'(`str` or `tuple`)').tag(wwt='lineColor')
line_width = AstropyQuantity(1*u.pixel,
line_width = AstropyQuantity(1 * u.pixel,
help='Assigns line width in pixels '
'(:class:`~astropy.units.Quantity`)').tag(wwt='lineWidth')
radius = AstropyQuantity(80*u.pixel,
radius = AstropyQuantity(80 * u.pixel,
help='Sets the radius for the circle '
'(:class:`~astropy.units.Quantity`)').tag(wwt='radius')
def __init__(self, parent=None, center=None, **kwargs):
super(Circle, self).__init__(parent, **kwargs)
if center:
self.set_center(center)
else:
self._center = parent.get_center().icrs
@validate('line_width')
def _validate_linewidth(self, proposal):
if proposal['value'].unit.is_equivalent(u.pixel):
@ -116,12 +144,7 @@ class Circle(Annotation):
self.parent._send_msg(event='circle_set_center', id=self.id,
ra=coord_icrs.ra.degree,
dec=coord_icrs.dec.degree)
def remove(self):
"""
Removes the specified annotation from the current view.
"""
self.parent._send_msg(event='remove_annotation', id=self.id)
self._center = coord_icrs
def _on_trait_change(self, changed):
if changed['name'] == 'radius':
@ -136,6 +159,14 @@ class Circle(Annotation):
super(Circle, self)._on_trait_change(changed)
def _serialize_state(self):
state = super(Circle, self)._serialize_state()
state['settings'].append({'name': 'skyRelative',
'value': self.radius.unit.is_equivalent(u.degree)})
state['center'] = {'ra': self._center.ra.deg,
'dec': self._center.dec.deg}
return state
class Polygon(Annotation):
"""
@ -154,10 +185,14 @@ class Polygon(Annotation):
'(`str` or `tuple`)').tag(wwt='fillColor')
line_color = Color('white', help='Assigns line color for the polygon '
'(`str` or `tuple`)').tag(wwt='lineColor')
line_width = AstropyQuantity(1*u.pixel,
line_width = AstropyQuantity(1 * u.pixel,
help='Assigns line width in pixels '
'(:class:`~astropy.units.Quantity`)').tag(wwt='lineWidth')
def __init__(self, parent=None, **kwargs):
self._points = []
super(Polygon, self).__init__(parent, **kwargs)
@validate('line_width')
def _validate_linewidth(self, proposal):
if proposal['value'].unit.is_equivalent(u.pixel):
@ -178,21 +213,17 @@ class Polygon(Annotation):
The coordinates of the desired point(s).
"""
coord_icrs = coord.icrs
if coord_icrs.isscalar: # if coord only has one point
if coord_icrs.isscalar: # if coord only has one point
self.parent._send_msg(event='polygon_add_point', id=self.id,
ra=coord_icrs.ra.degree,
dec=coord_icrs.dec.degree)
self._points.append(coord_icrs)
else:
for point in coord_icrs:
self.parent._send_msg(event='polygon_add_point', id=self.id,
ra=point.ra.degree,
dec=point.dec.degree)
def remove(self):
"""
Removes the specified annotation from the current view.
"""
self.parent._send_msg(event='remove_annotation', id=self.id)
self._points.append(point)
def _on_trait_change(self, changed):
if isinstance(changed['new'], u.Quantity):
@ -200,6 +231,14 @@ class Polygon(Annotation):
super(Polygon, self)._on_trait_change(changed)
def _serialize_state(self):
state = super(Polygon, self)._serialize_state()
state['points'] = []
for point in self._points:
state['points'].append({'ra': point.ra.degree,
'dec': point.dec.degree})
return state
class Line(Annotation):
"""
@ -214,10 +253,14 @@ class Line(Annotation):
color = ColorWithOpacity('white',
help='Assigns color for the line '
'(`str` or `tuple`)').tag(wwt='lineColor')
width = AstropyQuantity(1*u.pixel,
width = AstropyQuantity(1 * u.pixel,
help='Assigns width for the line in pixels '
'(:class:`~astropy.units.Quantity`)').tag(wwt='lineWidth')
def __init__(self, parent=None, **kwargs):
self._points = []
super(Line, self).__init__(parent, **kwargs)
@validate('width')
def _validate_width(self, proposal):
if proposal['value'].unit.is_equivalent(u.pixel):
@ -235,21 +278,17 @@ class Line(Annotation):
The coordinates of the desired point(s).
"""
coord_icrs = coord.icrs
if coord_icrs.isscalar: # if coord only has one point
if coord_icrs.isscalar: # if coord only has one point
self.parent._send_msg(event='line_add_point', id=self.id,
ra=coord_icrs.ra.degree,
dec=coord_icrs.dec.degree)
self._points.append(coord_icrs)
else:
for point in coord_icrs:
self.parent._send_msg(event='line_add_point', id=self.id,
ra=point.ra.degree,
dec=point.dec.degree)
def remove(self):
"""
Removes the specified annotation from the current view.
"""
self.parent._send_msg(event='remove_annotation', id=self.id)
self._points.append(point)
def _on_trait_change(self, changed):
if isinstance(changed['new'], u.Quantity):
@ -257,12 +296,21 @@ class Line(Annotation):
super(Line, self)._on_trait_change(changed)
def _serialize_state(self):
state = super(Line, self)._serialize_state()
state['points'] = []
for point in self._points:
state['points'].append({'ra': point.ra.degree,
'dec': point.dec.degree})
return state
class FieldOfView():
"""
A collection of polygon annotations. Takes the name of a pre-loaded
telescope and displays its field of view.
"""
# a more efficient method than CircleCollection of changing trait values?
def __init__(self, parent, telescope, center, rot, **kwargs):
@ -323,7 +371,7 @@ class FieldOfView():
if abs(dec) > 90:
if dec < -90:
decs[i] = -90. - (dec + 90.)
else: # dec > 90
else: # dec > 90
decs[i] = 90. - (dec - 90.)
ras[i] += 180.
@ -362,7 +410,7 @@ class CircleCollection():
self.points = points
else:
raise IndexError('For performance reasons, only 10,000 '
'annotations can be added at once for the time being.')
'annotations can be added at once for the time being.')
self.parent = parent
self.collection = []
self._gen_circles(self.points, **kwargs)
@ -384,8 +432,7 @@ class CircleCollection():
def _gen_circles(self, points, **kwargs):
for elem in points:
circle = Circle(self.parent, **kwargs)
circle.set_center(elem)
circle = Circle(self.parent, elem, **kwargs)
self.collection.append(circle)
def add_points(self, points, **kwargs):

Просмотреть файл

@ -51,6 +51,7 @@ class BaseWWTWidget(HasTraits):
self._paused = False
self._last_sent_view_mode = 'sky'
self.layers = LayerManager(parent=self)
self._annotation_set = set()
# NOTE: we deliberately don't force _on_trait_change to be called here
# for the WWT settings, as the default values are hard-coded in wwt.html
@ -146,6 +147,7 @@ class BaseWWTWidget(HasTraits):
"""
Clears all annotations from the current view.
"""
self._annotation_set.clear()
return self._send_msg(event='clear_annotations')
def get_center(self):
@ -452,9 +454,7 @@ class BaseWWTWidget(HasTraits):
attributes to be set upon shape initialization.
"""
# TODO: could buffer JS call here
circle = Circle(parent=self, **kwargs)
if center:
circle.set_center(center)
circle = Circle(parent=self, center=center, **kwargs)
return circle
def add_polygon(self, points=None, **kwargs):
@ -647,6 +647,10 @@ class BaseWWTWidget(HasTraits):
if self.current_mode in VIEW_MODES_3D:
self.solar_system._add_settings_to_serialization(state)
state['annotations'] = []
for annot in self._annotation_set:
state['annotations'].append(annot._serialize_state())
with open(file,'w') as file_obj:
json.dump(state,file_obj)

Просмотреть файл

@ -149,6 +149,8 @@ function loadWwtFigure() {
}
});
wwtIntialState['annotations'].forEach(loadAnnotation);
if (!viewSettings['tracked_object_id']) { //Not tracking or trivially track sun (id=0)
wwt.gotoRaDecZoom(viewSettings['ra'], viewSettings['dec'], viewSettings['fov'], true);
}
@ -216,6 +218,46 @@ function loadTableLayer(layerInfo) {
.done(onCsvLoad);
}
function loadAnnotation(annotation) {
var shape = annotation['shape'];
var id = annotation['id'];
wwt_apply_json_message(wwt, {
event: 'annotation_create',
shape: shape,
id: id
});
if (shape == "circle") {
wwt_apply_json_message(wwt, {
event: 'circle_set_center',
id: id,
ra: annotation['center']['ra'],
dec: annotation['center']['dec']
});
}
else {
annotation['points'].forEach(function (point) {
wwt_apply_json_message(wwt, {
event: shape + '_add_point',
id: id,
ra: point['ra'],
dec: point['dec']
});
});
}
annotation['settings'].forEach(function (setting) {
wwt_apply_json_message(wwt, {
event: 'annotation_set',
id: id,
setting: setting['name'],
value: setting['value']
});
})
}
function setHmtlSettings() {
if (wwtIntialState === undefined) { //JSON config file has not loaded yet, try again in 50 ms
setTimeout(setHmtlSettings, 50);