2
* @fileOverview Definition of a simple AJAX Request Loading Indicator UI component
3
* @author <a href="mailto:patrick.schmiedel@gmx.net">Patrick Schmiedel</a>
4
* @author <a href="mailto:keith.hughitt@gmail.com">Keith Hughitt</a>
6
/*global Class, Ajax, $*/
7
var LoadingIndicator = Class.create(
8
/** @lends LoadingIndicator.prototype */
12
* @description Creates a new LoadingIndicator
14
initialize: function () {
15
this.loadingItems = [];
17
Ajax.Responders.register({
18
onCreate: this.loadingStarted.bind(this, 'Ajax'),
19
onComplete: this.loadingFinished.bind(this, 'Ajax')
24
* @description Display the loading indicator
27
//Effect.Appear($('loading'), { duration: 1 });
32
* @description Hide the loading indicator
35
//Effect.Fade($('loading'), { duration: 1 });
40
* @description Reset the loading indicator
43
this.loadingItems.length = 0;
48
* @description Add an AJAX request to the loading stack
49
* @param {Object} e Event
51
loadingStarted: function (e) {
53
this.loadingItems.push({});
57
* @description Remove an AJAX request from the loading stack
58
* @param {Object} i Item to remove
60
loadingFinished: function (i) {
61
this.loadingItems.pop();
62
if (this.loadingItems.length === 0) {
67
* @fileOverview Contains the class definition for an UIElement class.
68
* @author <a href="mailto:patrick.schmiedel@gmx.net">Patrick Schmiedel</a>
71
var UIElement = Class.create(
72
/** @lends UIElement.prototype */
75
* @description Enables inheriting classes to use the "event/notification" system
78
initialize: function () {},
81
* @description Adds an event observer
82
* @param {String} eventName The name of the event to look for
83
* @param {Function} callback The function to execute when the event occurs
85
addObserver: function (eventName, callback) {
86
if (!this.observers) {
89
if (!this.observers[eventName]) {
90
this.observers[eventName] = $A([]);
92
this.observers[eventName].push(callback);
97
* @describe Fires a specific event
98
* @param {String} eventName The name of the event to trigger
99
* @param {Object} eventParameters The parameters to pass along with the event
101
fire: function (eventName, eventParameters) {
102
//$('output').innerHTML = eventName + ': ' + eventParameters;
103
if (!this.observers || !this.observers[eventName] || this.observers[eventName].length === 0) {
106
this.observers[eventName].each(function (callback) {
107
callback(eventParameters);
112
* @fileOverview "Abstract" Layer class.
113
* @author <a href="mailto:keith.hughitt@gmail.com">Keith Hughitt</a>
114
* @author <a href="mailto:patrick.schmiedel@gmx.net">Patrick Schmiedel</a>
116
/*global Layer, Class, UIElement, Element, $ */
117
var Layer = Class.create(UIElement,
118
/** @lends Layer.prototype */
120
maxZoomLevel: 20, // ZoomLevel where FullSize = 1px
126
* @description Creates a new Layer
127
* @param {Object} viewport Viewport to place the layer in
129
* <br><div style='font-size:16px'>Options:</div><br>
130
* <div style='margin-left:15px'>
131
* <b>maxZoomLevel</b> - Maximum zoom level supported by the layer<br>
132
* <b>minZoomLevel</b> - Minimum zoom level supported by the layer<br>
133
* <b>visible</b> - The default layer visibility<br>
136
initialize: function (viewport) {
137
this.viewport = viewport;
138
this.domNode = $(viewport.movingContainer.appendChild(new Element('div')));
139
this.viewport.addObserver('move', this.viewportMove.bind(this));
140
this.id = 'layer' + Math.floor(Math.random() * 100000 + 1);
144
* @description Adjust the Layer's z-index
145
* @param {Object} val Z-index to use
147
setZIndex: function (val) {
148
this.domNode.setStyle({ zIndex: val });
152
* @description Sets the Layer's visibility
153
* @param {Boolean} visible Hide/Show layer
154
* @returns {Boolean} Returns new setting
156
setVisible: function (visible) {
157
this.visible = visible;
158
this.domNode.setStyle({ visibility: (visible ? 'visible' : 'hidden') });
163
* @description Toggle layer visibility
165
toggleVisible: function () {
166
return this.setVisible(!this.visible);
169
* @author <a href="mailto:keith.hughitt@gmail.com">Keith Hughitt</a>
170
* @fileOverview This class extends the base jQuery UI datepicker to provide some basic data-binding and enable the class to work more efficiently with Helioviewer.
171
* @see ui.datepicker.js
173
* Syntax: jQuery, Prototype
175
/*global Class, Calendar, $, UIElement, jQuery, window */
176
var Calendar = Class.create(UIElement,
177
/** @lends Calendar.prototype */
180
* @description Creates a new Calendar.
181
* @param {Object} controller Reference to the controller class (Helioviewer).
182
* @param {String} dateFieldId The ID of the date form field associated with the Calendar.
183
* @param {String} timeFieldId The ID of the time form field associated with the Calendar.
186
initialize: function (controller, dateFieldId, timeFieldId) {
187
this.controller = controller;
188
this.dateField = $(dateFieldId);
189
this.timeField = $(timeFieldId);
192
this.cal = jQuery("#" + dateFieldId).datepicker({
193
buttonImage: 'images/calendar_small.png',
194
buttonImageOnly: true,
195
buttonText: "Select a date.",
196
dateFormat: 'yy/mm/dd',
199
yearRange: '1998:2008',
200
onSelect: function (dateStr) {
201
window.setTimeout(function () {
202
var time = self.timeField.value,
203
date = Date.parse(dateStr),
205
//Factor in time portion of timestamp
206
hours = parseInt(time.substring(0, 2), 10),
207
minutes = parseInt(time.substring(3, 5), 10),
208
seconds = parseInt(time.substring(6, 8), 10),
211
utcDate = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate(), hours, minutes, seconds));
213
self.fire('observationDateChange', utcDate);
221
* @description Updates the HTML form fields associated with the calendar.
223
updateFields: function () {
224
// Set text-field to the new date
225
this.dateField.value = this.controller.date.toYmdUTCString();
226
this.timeField.value = this.controller.date.toHmUTCString();
230
* @author <a href="mailto:keith.hughitt@gmail.com">Keith Hughitt</a>
231
* @fileOverview Contains the class definition for an EventLayer class.
232
* @see Layer, TileLayer
234
* Syntax: jQuery, Prototype
236
/*global Class, Layer, EventLayer, $, jQuery, $A, Ajax, Element, EventMarker, navigator */
237
var EventLayer = Class.create(Layer,
238
/** @lends EventLayer.prototype */
241
* @description Default EventLayer options<br><br>
242
* <strong>Notes:</strong><br><br>
243
* "sunRadius0" refers to the Sun's radius at zoom-level 0, in pixels = 94 * 2^12.<br>
244
* "windowSize" refers to the size of the window (in seconds) from which events should be displayed.<br>
248
sunRadius0: 94 * (1 << 12),
250
displayLabels: false,
260
* @description Creates a new EventLayer.
261
* @param {Object} viewport A reference to Helioviewer's viewport.
262
* @param {Object} options The event layer settings.
264
initialize: function (viewport, options) {
265
Object.extend(this, this.defaultOptions);
266
Object.extend(this, options);
267
this.viewport = viewport;
269
this.id = 'eventlayer' + new Date().getTime();
271
this.events = $A([]);
273
this.domNode = new Element('div', {className: 'event-layer'});
274
this.viewport.movingContainer.appendChild(this.domNode);
276
// Add to eventLayer Accordion (if is not already there).
277
this.eventAccordion.addLayer(this);
283
* @description Sends an AJAX request to get a list of events.
285
queryEvents: function () {
287
var processResponse = function (transport) {
289
if (transport.responseJSON) {
290
this.displayEvents(transport.responseJSON);
293
xhr, queryDate = null;
295
// Work-around: In Firefox 3.1, Date.toISOString() Returns single-quoted strings
296
// http://code.google.com/p/datejs/issues/detail?id=54
297
if (navigator.userAgent.search(/3\.1/) !== -1) {
298
queryDate = this.viewport.controller.date.toISOString();
301
queryDate = this.viewport.controller.date.toISOString().slice(1, -1);
305
xhr = new Ajax.Request(this.viewport.controller.eventAPI, {
307
onSuccess: processResponse.bind(this),
311
windowSize: this.windowSize,
312
catalogs: this.catalog
318
* @description Place-holder for an event-handler which will handle viewportMove events.
320
viewportMove: function () {
325
* @description Removes all events from the screen and clears event container
328
this.events.each(function (e) {
335
* @description Draws events to screen
336
* @param {JSON} jsonEvents An JSON array of event meta-information
338
displayEvents: function (jsonEvents) {
340
date = this.viewport.controller.date,
341
UTCOffset = - parseInt(date.getTimezoneOffset(), 10) * 60,
342
sunRadius = this.sunRadius0 >> this.viewport.zoomLevel;
344
// Stylize each event marker based on it's the event type
345
$A(jsonEvents).each(function (event) {
346
self.events.push(new EventMarker(self, event, date, UTCOffset, sunRadius, {offset: self.eventMarkerOffset}));
351
* @description Updates the icon associated with the EventLayer.
352
* @param {String} newIcon New icon to use.
354
updateIcon: function (newIcon) {
355
this.icon = "small-" + newIcon;
356
var type = this.eventAccordion.eventCatalogs.get(this.catalog).eventType.gsub(' ', "_"),
357
url = 'url(images/events/small-' + newIcon + '-' + type + '.png)';
359
// Update event markers
360
this.events.each(function (event) {
361
event.marker.setStyle({
366
// Update event accordion icon
367
jQuery('#event-icon-' + this.id).css('background', url);
369
// Update user's stored settings
370
this.eventAccordion.eventIcons[this.catalog] = "small-" + newIcon;
371
this.viewport.controller.userSettings.set('event-icons', this.eventAccordion.eventIcons);
375
* @description Toggle event label visibility
377
toggleLabelVisibility: function () {
378
this.displayLabels = !this.displayLabels;
379
this.events.each(function (e) {
385
* @description Reload event-layer
387
reload: function () {
392
* @description Reset event-layer
395
var sunRadius = this.sunRadius0 >> this.viewport.zoomLevel;
397
this.events.each(function (e) {
398
e.refresh(sunRadius);
403
* @author <a href="mailto:keith.hughitt@gmail.com">Keith Hughitt</a>
404
* @fileOverview Contains the class definition for an EventLayerAccordion class.
405
* @see EventLayer, LayerManager, TileLayerAccordion
406
* @requires ui.dynaccordion.js
408
* Syntax: jQuery, Prototype
410
/*global Class, EventLayerAccordion, $, jQuery, Ajax, Event, EventLayer, Hash, IconPicker, Layer */
411
var EventLayerAccordion = Class.create(Layer,
412
/** @lends EventLayerAccordion.prototype */
416
* @description Creates a new EventLayerAccordion
417
* @param {Object} viewport A reference to Helioviewer's viewport.
418
* @param {String} containerId The ID of the container where the EventLayerAccordion should be placed.
420
initialize: function (viewport, containerId) {
421
this.viewport = viewport;
422
this.container = jQuery('#' + containerId);
425
this.eventIcons = this.viewport.controller.userSettings.get('event-icons');
427
// Setup menu UI components
431
this.iconPicker = new IconPicker('event-icon-menu');
433
// Initialize accordion
434
this.domNode = jQuery('#EventLayerAccordion-Container');
435
this.domNode.dynaccordion();
437
// Get Event Catalogs
438
this.getEventCatalogs();
442
* @description Adds a new entry to the event layer accordion
443
* @param {Object} layer EventLayer to add to the accordion.
445
addLayer: function (layer) {
446
// Create accordion entry header
447
var catalog = this.eventCatalogs.get(layer.catalog),
448
etype = catalog.eventType.gsub(' ', '_'),
449
icon, visibilityBtn, removeBtn, head, body;
451
layer.icon = this.eventIcons[catalog.id] || 'small-blue-circle';
453
icon = "| <button class='event-accordion-icon' id='event-icon-" + layer.id + "' style='background: url(images/events/" + layer.icon + "-" + etype + ".png);'></button>";
454
visibilityBtn = "<button class='layerManagerBtn visible' id='visibilityBtn-" + layer.id + "' value=true type=button title='toggle layer visibility'></button>";
455
removeBtn = "<button class='layerManagerBtn remove' id='removeBtn-" + layer.id + "' type=button title='remove layer'></button>";
456
head = "<div class=layer-Head><span class=event-accordion-header-left>" + catalog.name + "</span><span class=event-accordion-header-right>" + icon + visibilityBtn + removeBtn + "</span></div>";
458
// Create accordion entry body
459
body = '<div style="color: white; position: relative;">' + catalog.description + '</div>';
462
this.domNode.dynaccordion("addSection", {id: layer.id, header: head, cell: body});
465
this._setupEventHandlers(layer);
469
* @description Get a list of the available event catalogs.
471
getEventCatalogs: function () {
472
//@TODO: Move this to the new layer manager.
475
var processResponse = function (transport) {
476
var lm = this.viewport.controller.layerManager,
477
catalogs = transport.responseJSON,
480
if (typeof(catalogs) !== "undefined") {
481
this.eventCatalogs = new Hash();
483
catalogs.each(function (catalog) {
484
//Ignore EIT Activity reports for the time being
485
if (catalog.id !== "EITPlanningService::EITActivity") {
486
self.eventCatalogs.set(catalog.id, catalog);
490
//Initial catalogs to load
491
lm.addLayer(new EventLayer(this.viewport, {
492
catalog: "VSOService::cmelist",
495
lm.addLayer(new EventLayer(this.viewport, {
496
catalog: "GOESXRayService::GOESXRay",
499
lm.addLayer(new EventLayer(this.viewport, {
500
catalog: "VSOService::noaa",
501
eventAccordion: this,
505
this._catalogsUnavailable();
509
xhr = new Ajax.Request(this.viewport.controller.eventAPI, {
511
parameters: { action: "getEventCatalogs" },
512
onSuccess: processResponse.bind(this)
517
* @description Setup empty event layer accordion.
519
_setupUI: function () {
520
// Create a top-level header and an "add layer" button
521
var title = jQuery('<span class="accordion-title">Features/Events</span>').css({'float': 'left'}),
522
addLayerBtn = jQuery('<a href=# class=gray>[Add]</a>').css({'margin-right': '14px'}),
524
this.container.append(jQuery('<div></div>').css('text-align', 'right').append(title).append(addLayerBtn));
525
this.container.append(jQuery('<div id="EventLayerAccordion-Container"></div>'));
528
addLayerBtn.click(function () {
529
if (typeof(self.eventCatalogs) !== "undefined") {
530
//Populate select-box
531
var select = self._buildCatalogSelect(),
533
okayBtn = "<button>Ok</button>",
536
tmpId = 'tmp' + new Date().getTime();
537
self.domNode.dynaccordion("addSection", {
539
header: '<div class="layer-Head">Select catalog to use:</div>',
540
cell: select + okayBtn,
544
// Swap out for feature/event catalog when user changes
545
Event.observe($(tmpId).select('button').first(), 'click', function (e) {
546
var select = $(tmpId).select('select').first();
547
self.domNode.dynaccordion('removeSection', {
550
self.viewport.controller.layerManager.addLayer(new EventLayer(self.viewport, {
551
catalog: select.value,
562
* @description Builds a SELECT menu with all the catalogs not already displayed.
564
_buildCatalogSelect: function () {
566
select = "<select class='event-layer-select' style='margin:5px;'>";
568
this.eventCatalogs.each(function (catalog) {
569
if (!self.viewport.controller.layerManager.hasEventCatalog(catalog.key)) {
570
select += "<option value=" + catalog.key + ">" + catalog.value.name + "</option>";
573
select += "</select>";
579
* @description Display error message to let user know service is down.
581
_catalogsUnavailable: function () {
583
$('eventAccordion').select('a.gray')[0].update("");
586
var head = "<div class='layer-Head'>Currently Unavailable</div>",
587
body = "<span style='color: #FFF;'>Helioviewer's Feature/Event catalog system is currently unavailable. Please try again later.</span>";
588
this.domNode.dynaccordion("addSection", {id: 404, header: head, cell: body});
592
* @description Sets up UI-related event handlers
593
* @param {Object} layer EventLayer being added to the accordion.
595
_setupEventHandlers: function (layer) {
596
var visibilityBtn = jQuery("#visibilityBtn-" + layer.id),
597
removeBtn = jQuery("#removeBtn-" + layer.id),
598
eventIcon = jQuery("#event-icon-" + layer.id),
603
* @description Toggles layer visibility
604
* @param {Object} e jQuery Event Object.
606
toggleVisibility = function (e) {
607
var visible = layer.toggleVisible(),
608
icon = (visible ? 'LayerManagerButton_Visibility_Visible.png' : 'LayerManagerButton_Visibility_Hidden.png');
609
jQuery("#visibilityBtn-" + layer.id).css('background', 'url(images/blackGlass/' + icon + ')');
615
* @description Layer remove button event-handler
616
* @param {Object} e jQuery Event Object.
618
removeLayer = function (e) {
620
self.viewport.controller.layerManager.removeLayer(layer);
621
self.domNode.dynaccordion('removeSection', {id: layer.id});
627
* @description Icon selection menu event-handler
628
* @param {Object} e jQuery Event Object.
630
showIconMenu = function (e) {
632
self.iconPicker.toggle(layer, jQuery(this).position());
636
visibilityBtn.bind('click', this, toggleVisibility);
637
removeBtn.bind('click', this, removeLayer);
638
eventIcon.bind('click', layer, showIconMenu);
643
* @author <a href="mailto:keith.hughitt@gmail.com">Keith Hughitt</a>
644
* @fileoverview Contains the class definition for an EventMarker class.
649
/*global EventMarker, Class, $, $$, $H, Element, Event, Tip */
650
var EventMarker = Class.create(
651
/** @lends EventMarker.prototype */
655
* @description Creates a new EventMarker
656
* @param {Object} eventLayer EventLayer associated with the EventMarker
657
* @param {JSON} event Event details
658
* @param {Date} date The date when the given event occured
659
* @param {Int} utcOffset The UTC offset for the system's local time
660
* @param {Int} sunRadius The radius of the sun in pixels
661
* @param {Object} options Extra EventMarker settings to use
663
initialize: function (eventLayer, event, date, utcOffset, sunRadius, options) {
664
Object.extend(this, event);
665
Object.extend(this, options);
667
this.eventLayer = eventLayer;
669
this.utcOffset = utcOffset;
670
this.sunRadius = sunRadius;
672
//Determine event type and catalog
673
var catalogs = eventLayer.eventAccordion.eventCatalogs;
674
this.catalogName = catalogs.get(this.catalogId).name;
675
this.type = catalogs.get(this.catalogId).eventType;
679
x: this.sunX * sunRadius,
680
y: this.sunY * sunRadius
683
this.container = this.eventLayer.domNode.appendChild(
684
new Element('div', {className: 'event', style: 'left: ' + (this.pos.x) + 'px; top: ' + (this.pos.y) + 'px;'})
687
//Create dom-nodes for event marker, details label, and details popup
694
* @description Creates the marker and adds it to the viewport
696
createMarker: function () {
697
//make event-type CSS-friendly
698
var cssType = this.type.gsub(' ', "_"),
700
//create html dom-node
701
marker = new Element('div', {className: 'event-marker'});
704
'background': 'url(images/events/' + this.eventLayer.icon + "-" + cssType + '.png)'
708
//marker.observe('click', function(event) {
709
// self.togglePopup();
712
this.marker = marker;
714
this.container.appendChild(marker);
718
* @description Creates a small block of text which is displayed when the user pressed the "d" key ("details").
720
createLabel: function () {
721
var display = (this.eventLayer.displayLabels ? "inline" : "none"),
722
labelText = this.getLabelText(this.type),
724
//Determine time difference between desired time and event time
725
eventDate = Date.parse(this.time.startTime.substr(0, 19)).addSeconds(this.utcOffset),
726
timeDiff = eventDate.getTime() - this.appDate.getTime(),
728
//Create a hidden node with the events ID to be displayed upon user request
729
label = new Element('div', {className: 'event-label'}).setStyle({'display': display}).insert(labelText);
731
//Adjust style to reflect time difference
733
label.addClassName("timeBehind");
735
else if (timeDiff > 0) {
736
label.addClassName("timeAhead");
741
this.container.appendChild(label);
746
* @description Choses the text to display in the details label based on the type of event
747
* @param {String} eventType The type of event for which a label is being created
749
getLabelText: function (eventType) {
750
var labelText = null;
754
case "Active Region":
755
labelText = this.eventId;
758
labelText = this.time.startTime;
760
case "Type II Radio Burst":
761
labelText = this.time.startTime;
764
labelText = this.time.startTime;
772
createPopup: function () {
773
var properties = $H(this.properties);
775
var size = (properties.size() > 2 ? 'larger' : 'large');
778
var popup = new Element('div', {className: 'event-popup tooltip-topleft-' + size, style: 'display: none;'});
779
var content = "<div class='event-popup-container'>" +
780
"<strong>" + this.type + " " + this.eventId + "</strong><br>" +
781
"<p>" + this.catalogName + "</p><br>" +
782
"<strong>start:</strong> " + this.time.startTime + "<br>" +
783
"<strong>end:</strong> " + this.time.endTime + "<br><br>" +
784
"<strong>Position Angle:</strong> " + this.polarCpa + "° " +
785
"<strong>Width:</strong> " + this.polarWidth + "°<br>";
786
properties.keys().each(function(key){
787
content += "<strong>" + key + ":</strong> " + properties.get(key) + "<br>";
789
content += "<strong>Source:</strong> <a href='" + this.sourceUrl + "' class='event-url' target='_blank'>" + this.sourceUrl + "</a><br></div>";
791
popup.update(content);
794
var closeBtn = new Element('a', {className: 'event-popup-close-btn'}).insert("x");
795
closeBtn.observe('click', this.togglePopup.bind(this));
796
popup.insert(closeBtn);
799
this.container.appendChild(popup);
803
* @description Creates a popup which is displayed when the event marker is clicked
805
createPopup: function () {
806
var properties = $H(this.properties), t,
807
content = "<div class='event-popup-container'>" +
808
"<strong>" + this.type + " " + this.eventId + "</strong><br>" +
809
"<p>" + this.catalogName + "</p><br>" +
810
"<strong>start:</strong> " + this.time.startTime + "<br>" +
811
"<strong>end:</strong> " + this.time.endTime + "<br><br>";
813
//"<strong>Position Angle:</strong> " + this.polarCpa + "° " +
814
//"<strong>Width:</strong> " + this.polarWidth + "°<br>";
816
properties.keys().each(function (key) {
817
content += "<strong>" + key + ":</strong> " + properties.get(key);
818
if (key.search(/angle/i) !== -1) {
823
content += "<strong>Source:</strong> <a href='" + this.sourceUrl + "' class='event-url' target='_blank'>" + this.sourceUrl + "</a><br></div>";
825
t = new Tip(this.container, content, {
832
hook: { target: 'bottomRight', tip: 'topLeft' },
833
offset: { x: 14, y: 14 }
836
//Work-around: Move the tooltip dom-node into the event-marker node so that is follows when dragging.
837
this.container.observeOnce('click', function (e) {
838
var tip = $$('body > .prototip');
839
//IE7: above selector doesn't always return hit
840
if (tip.length > 0) {
841
this.insert(tip.first().remove().setStyle({'top': '12px', 'left': '8px'}));
842
Event.observe(this, 'click', function (e) {
843
$(this.select('.prototip')).first().setStyle({'top': '12px', 'left': '8px'});
849
// jQuery implementation
850
jQuery(this.container).one('click', function(e) {
851
this.insert($$('body > .prototip').first().remove().setStyle({'top': '12px', 'left': '8px'}));
852
Event.observe(this, 'click', function (e) {
853
this.select('.prototip').first().setStyle({'top': '12px', 'left': '8px'});
858
// Alternative Prototype implementation:
859
Event.observe(this.container, 'click', function(e) {
860
if ($$('body > .prototip').length > 0) {
861
e.target.insert($$('body > .prototip').first().remove());
863
e.target.select('.prototip').first().setStyle({
871
* @description Removes the EventMarker
873
remove: function () {
874
this.container.remove();
878
* @description Redraws event
879
* @param {Int} sunRadius The updated solar radius, in pixels.
881
refresh: function (sunRadius) {
882
this.sunRadius = sunRadius;
884
x: this.sunX * sunRadius,
885
y: this.sunY * sunRadius
887
this.container.setStyle({
888
left: (this.pos.x - 2) + 'px',
889
top: (this.pos.y - 2) + 'px'
894
* @description Toggles event label visibility
896
toggleLabel: function () {
901
* @description Toggle event popup visibility
903
togglePopup: function () {
908
* @author <a href="mailto:keith.hughitt@gmail.com">Keith Hughitt</a>
909
* @fileOverview This class extends the base Simile Timeline
910
* @see http://code.google.com/p/simile-widgets/
914
/*global Class, $, EventTimeline, Timeline, UIElement, window */
915
var EventTimeline = Class.create(UIElement,
916
/** @lends EventTimeline.prototype */
919
* @description Creates a new EventTimeline.
920
* @param {Object} controller Reference to the controller class (Helioviewer).
921
* @param {String} container The ID for the timeline's container.
924
initialize: function (controller, container) {
925
this.controller = controller;
926
this.container = container;
927
this.resizeTimerID = null;
929
this.eventSource = new Timeline.DefaultEventSource();
931
Timeline.createBandInfo({
932
eventSource: this.eventSource,
934
intervalUnit: Timeline.DateTime.MONTH,
937
Timeline.createBandInfo({
938
eventSource: this.eventSource,
940
intervalUnit: Timeline.DateTime.YEAR,
946
bandInfos[1].syncWith = 0;
947
bandInfos[1].highlight = true;
948
this.timeline = Timeline.create($(this.container), bandInfos);
949
this.timeline.loadJSON("http://localhost/dev/test.json", function (json, url) {
950
self.eventSource.loadJSON(json, url);
955
* @description Event-hanlder for window resize
957
resize: function () {
958
if (this.resizeTimerID === null) {
959
this.resizeTimerID = window.setTimeout(function () {
960
this.resizeTimerID = null;
961
this.timeline.layout();
966
* @fileOverview Contains the main application class and controller for Helioviewer.
967
* @author <a href="mailto:keith.hughitt@gmail.com">Keith Hughitt</a>
968
* @author <a href="mailto:patrick.schmiedel@gmx.net">Patrick Schmiedel</a>
970
/*global Helioviewer, Class, $, $A, Builder, Calendar, Event, EventLayerAccordion, EventTimeline, LayerManager, MessageConsole,TileLayer, TileLayerAccordion, TimeControls, UserSettings, ZoomControl, jQuery, LoadingIndicator, Viewport, document, window */
971
var Helioviewer = Class.create(
972
/** @lends Helioviewer.prototype */
975
* @description Default Helioviewer options<br><br>
978
defaultZoomLevel: 12,
981
defaultPrefetchSize: 0,
982
timeIncrementSecs: 86400,
985
tileAPI: 'api/index.php',
986
imageAPI: 'api/index.php',
987
eventAPI: 'api/index.php'
992
* @description Creates a new Helioviewer instance.
993
* @param {Object} options Custom application settings.
995
* <br><div style='font-size:16px'>Options:</div><br>
996
* <div style='margin-left:15px'>
997
* <b>defaultZoomLevel</b> - The initial zoom-level to display.<br>
998
* <b>defaultPrefetchSize</b> - The radius outside of the visible viewport to prefetch.<br>
999
* <b>timeIncrementSecs</b> - The default amount of time to move when the time navigation arrows are pressed.<br>
1000
* <b>minZoomLevel</b> - Minimum zoom level allowed.<br>
1001
* <b>maxZoomLevel</b> - Maximum zoom level allowed.<br>
1002
* <b>imageAPI</b> - URL to the API for retrieving image meta information.<br>
1003
* <b>tileAPI</b> - URL to the API for retrieving tiles.<br>
1004
* <b>eventAPI</b> - URL to the API for retrieving events and event-catalogs.<br>
1006
* @see Helioviewer#defaultOptions for a list of the available parameters.
1008
initialize: function (options) {
1009
Object.extend(this, this.defaultOptions);
1010
Object.extend(this, options);
1012
// Loading indication
1013
this.loadingIndicator = new LoadingIndicator();
1015
// Load user-settings
1016
this.loadUserSettings();
1019
this.date = new Date(this.userSettings.get('obs-date'));
1020
$('date').writeAttribute('value', this.date.toYmdUTCString());
1021
$('time').writeAttribute('value', this.date.toHmUTCString());
1023
this.layerManager = new LayerManager(this);
1024
this.initViewports();
1027
this.initKeyBoardListeners();
1029
// Add initial layers
1030
this.userSettings.get('tile-layers').each((function (settings) {
1031
this.layerManager.addLayer(new TileLayer(this.viewports[0], settings));
1035
//Shadowbox.init({skipSetup: true});
1040
* @description Initialize Helioviewer's user interface (UI) components
1042
initUI: function () {
1043
var centerBtn, mouseCoords;
1046
this.calendar = new Calendar(this, 'date', 'time');
1049
this.zoomControl = new ZoomControl(this, {
1051
zoomLevel: this.userSettings.get('zoom-level'),
1052
minZoomLevel: this.minZoomLevel,
1053
maxZoomLevel: this.maxZoomLevel
1056
//Time-navigation controls
1057
this.timeControls = new TimeControls(this, 'timestep-select', 'timeBackBtn', 'timeForwardBtn', this.timeIncrementSecs);
1060
this.messageConsole = new MessageConsole(this, 'message-console', 'helioviewer-viewport-container-outer');
1062
//Tile & Event Layer Accordions (accordions must come before LayerManager instance...)
1063
this.tileLayerAccordion = new TileLayerAccordion(this.layerManager, 'layerManager');
1064
this.eventLayerAccordion = new EventLayerAccordion(this.viewports[0], 'eventAccordion');
1067
this.initToolTips();
1070
centerBtn = Builder.node('div', {className: 'center-button'}, Builder.node('span', {}, "center"));
1071
Event.observe(centerBtn, 'click', this.viewports[0].center.bindAsEventListener(this.viewports[0]));
1072
this.viewports[0].innerNode.insert(centerBtn);
1075
mouseCoords = Builder.node('div', {id: 'mouse-coords', style: 'display: none'});
1077
// IE will choke here if we don't manually extend mouseCoords... see last paragraph at http://prototypejs.org/learn/extensions
1078
Element.extend(mouseCoords);
1080
mouseCoords.insert(Builder.node('div', {id: 'mouse-coords-x', style: 'width:50%; float: left'}));
1081
mouseCoords.insert(Builder.node('div', {id: 'mouse-coords-y', style: 'width:50%; float: left'}));
1082
this.viewports[0].innerNode.insert(mouseCoords);
1085
jQuery('#about-dialog').dialog({
1087
title: "Helioviewer - About",
1092
jQuery('#helioviewer-about').click(function (e) {
1093
if (jQuery('#about-dialog').dialog('isOpen')) {
1094
jQuery('#about-dialog').dialog('close');
1097
jQuery('#about-dialog').dialog('open');
1101
//Keyboard shortcuts dialog
1102
jQuery('#keyboard-shortcuts-dialog').dialog({
1104
title: "Helioviewer - Usage Tips",
1109
jQuery('#helioviewer-shortcuts').click(function (e) {
1110
if (jQuery('#keyboard-shortcuts-dialog').dialog('isOpen')) {
1111
jQuery('#keyboard-shortcuts-dialog').dialog('close');
1114
jQuery('#keyboard-shortcuts-dialog').dialog('open');
1119
//this.movieBuilder = new MovieBuilder({id: 'movieBuilder', controller: this});
1122
//this.timeline = new EventTimeline(this, "timeline");
1126
* @description Loads user settings from cookies or defaults if no settings have been stored.
1128
loadUserSettings: function () {
1129
this.userSettings = new UserSettings(this);
1131
// Load any view parameters specified via API
1132
if (this.load["obs-date"]) {
1133
this.userSettings.set('obs-date', parseInt(this.load["obs-date"], 10) * 1000);
1136
if (this.load["img-scale"]) {
1137
this.userSettings.set('zoom-level', this.scaleToZoomLevel(parseInt(this.load["img-scale"], 10)));
1140
if (this.load.layers) {
1142
$A(this.load.layers).each(function (layer) {
1143
layers.push({ tileAPI: "api/index.php", observatory: layer.substr(0, 3), instrument: layer.substr(3, 3), detector: layer.substr(6, 3), measurement: layer.substr(9, 3) });
1146
this.userSettings.set('tile-layers', layers);
1152
* @description Initialize Helioviewer's viewport(s).
1154
initViewports: function () {
1155
this.viewports = $A([
1156
new Viewport(this, { id: this.viewportId, zoomLevel: this.userSettings.get('zoom-level'), prefetch: this.defaultPrefetchSize, debug: false })
1159
// Dynamically resize the viewport when the browser window is resized.
1160
this.viewports.each(function (viewport) {
1161
Event.observe(window, 'resize', viewport.resize.bind(viewport));
1166
* @description Initialize event-handlers for UI components controlled by the Helioviewer class
1168
initEvents: function () {
1169
this.observe(this.zoomControl, 'change', this.handlers.zoom);
1170
this.observe(this.calendar, 'observationDateChange', this.handlers.observationDateChange);
1171
this.observe(this.layerManager, 'newLayer', this.handlers.newLayer);
1172
Event.observe(this.calendar.timeField, 'change', this.handlers.observationTimeChange.bindAsEventListener(this));
1176
* @description Initialize keyboard-related event handlers.
1177
* @see Based off method by <a href="http://www.quirksmode.org/js/events_properties.html#key">PPK</a>
1179
initKeyBoardListeners: function () {
1181
Event.observe(document, 'keypress', function (e) {
1183
//Ignore event if user is type in an input form field
1184
if (e.target.tagName !== "INPUT") {
1185
var code, character;
1196
character = String.fromCharCode(code);
1198
//TODO: use events or public method instead of zoomControl's (private) method.
1199
if (character === "-" || character === "_") {
1200
self.zoomControl.zoomButtonClicked(+1);
1202
else if (character === "=" || character === "+") {
1203
self.zoomControl.zoomButtonClicked(-1);
1205
else if (character === "c") {
1206
self.viewports.each(function (viewport) {
1210
//event label visibility toggle
1211
else if (character === "d") {
1212
self.layerManager.layers.each(function (layer) {
1213
if (layer.type === "EventLayer") {
1214
layer.toggleLabelVisibility();
1218
// toggle mouse-coords display
1219
else if (character === "m") {
1220
self.viewports.each(function (viewport) {
1221
viewport.ViewportHandlers.toggleMouseCoords();
1230
* @description Adds tooltips to all elements that are loaded everytime (buttons, etc) using default tooltip options.
1232
initToolTips: function () {
1234
'#zoomControlZoomIn',
1235
'#zoomControlZoomOut',
1236
'#zoomControlHandle',
1242
items.each(function (item) {
1243
self.addToolTip(item, {yOffset: -125});
1246
//Handle some special cases separately
1247
this.addToolTip('#movieBuilder', {position: 'topleft'});
1252
* @description Adds a tooltip with specified settings to a given component.
1253
* @param {String} CSS selector of th element to add ToolTip to.
1254
* @param {Hash} A hash containing any options configuration parameters to use.
1256
addToolTip: function (id, params) {
1257
var options = params || [],
1258
classname = "tooltip-" + (options.position || "bottomleft") + "-" + (options.tooltipSize || "medium");
1260
jQuery(id).tooltip({
1261
delay: (options.delay ? options.delay : 1000),
1262
track: (options.track ? options.track : false),
1267
extraClass: classname,
1268
top: (options.yOffset ? options.yOffset : 0),
1274
* @description Adds a new viewport to the list of maintained viewports: Usually only one will exist at a given time.
1275
* @param {Object} viewport A reference to the viewport instance to add.
1277
addViewport: function (viewport) {
1278
this.viewports.push(viewport);
1283
* @description Add an event-handler to a given component.
1284
* @param {Object} uielement The UI component to attach the event to
1285
* @param {String} eventName The name of the event to monitor for
1286
* @param {Function} eventHandler A reference to the event-handler
1289
observe: function (uielement, eventName, eventHandler) {
1290
uielement.addObserver(eventName, eventHandler.bind(this));
1294
* @description Sets the desired viewing date and time.
1295
* @param {Date} date A JavaScript Date object with the new time to use
1297
setDate: function (date) {
1299
var ts = Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), date.getUTCHours(), date.getUTCMinutes(), date.getUTCSeconds());
1300
this.userSettings.set('obs-date', parseInt(ts, 10));
1301
this.layerManager.reloadLayers();
1305
* @description Finds the closest support zoom-level to a given pixel scale (arcseconds per pixel)
1306
* @param {Float} imgScale The image scale in arcseconds per pixel
1308
scaleToZoomLevel: function (imgScale) {
1309
var zoomOffset = Math.round(Math.lg((imgScale / this.baseScale)));
1310
return this.baseZoom + zoomOffset;
1315
* @description Helioviewer application-level event handlers
1319
* @description Changes the zoom-level to a new value
1320
* @param {Int} level The new zoom-level to use
1322
zoom: function (level) {
1323
this.viewports.each(function (viewport) {
1324
viewport.zoomTo(level);
1328
observationDateChange: function (date) {
1330
this.calendar.updateFields();
1333
observationTimeChange: function (e) {
1334
var time = e.target.value,
1335
regex, newTime, hours, mins, secs;
1337
//make sure time entered in correct format
1338
regex = /^\d{2}:\d{2}:\d{2}?/;
1340
//Check to see if the input is a valid time
1341
if (time.match(regex)) {
1342
//Get the difference in times and add to this.date
1343
newTime = time.split(':');
1344
hours = parseInt(newTime[0], 10) - this.date.getUTCHours();
1345
mins = parseInt(newTime[1], 10) - this.date.getUTCMinutes();
1346
secs = parseInt(newTime[2], 10) - this.date.getUTCSeconds();
1348
this.date.addHours(hours);
1349
this.date.addMinutes(mins);
1350
this.date.setSeconds(secs);
1352
this.setDate(this.date);
1355
this.messageConsole.warn('Invalid time. Please enter a time in of form HH:MM:SS');
1359
newToolTip: function (tooltip) {
1360
this.addToolTip(tooltip.id, tooltip.params);
1364
* @description newLayer Initializes new layer upon user request
1365
* @param {Object} instrument Contains necessary information for creating a new layer
1366
* @param {LayerManagerMenuEntry} menuEntry Reference to the menu entry to allow layer to be tied to the entry
1368
newLayer: function (data) {
1369
var inst = data.instrument,
1370
ui = data.menuEntry,
1371
viewport = this.viewports[0],
1374
// Inialize layer and add it to the viewport
1375
layer = new TileLayer(viewport, { tileAPI: this.tileAPI, observatory: inst.observatory, instrument: inst.instrument, detector: inst.detector, measurement: inst.measurement });
1376
viewport.addLayer(layer);
1378
//Update menu entry display
1380
ui.displayTileLayerOptions();
1386
* @fileOverview Various helper functions used throughout Helioviewer.
1387
* @author <a href="mailto:patrick.schmiedel@gmx.net">Patrick Schmiedel</a>
1388
* @author <a href="mailto:keith.hughitt@gmail.com">Keith Hughitt</a>
1390
/*global Event, Element, navigator */
1392
* @description Pads a string to the left.
1393
* @param {String} padding Character to use for padding, e.g. " "
1394
* @param {Int} minLength Length to pad up to
1395
* @returns {String} The resulting string.
1397
String.prototype.padLeft = function (padding, minLength) {
1400
while (str.length < minLength) {
1406
* @description Trims a string from the left.
1407
* @param {String} padding Character to trim.
1408
* @returns {String} The resulting string.
1410
String.prototype.trimLeft = function (padding) {
1413
while (str[0] === pad) {
1414
str = str.substr(1);
1420
* @description Outputs a UTC Date string of the format "YYYY/MM/dd"
1421
* @returns {String} Datestring.
1423
Date.prototype.toYmdUTCString = function () {
1424
var year = this.getUTCFullYear() + '',
1425
month = (this.getUTCMonth() + 1) + '',
1426
day = this.getUTCDate() + '';
1427
return year + '/' + month.padLeft(0, 2) + '/' + day.padLeft(0, 2);
1431
* @description Outputs a UTC Date string of the format "HH:mm:ss"
1432
* @returns {String} Datestring.
1434
Date.prototype.toHmUTCString = function () {
1435
var hour = this.getUTCHours() + '',
1436
min = this.getUTCMinutes() + '',
1437
sec = this.getUTCSeconds() + '';
1438
return hour.padLeft(0, 2) + ':' + min.padLeft(0, 2) + ':' + sec.padLeft(0, 2);
1442
* @description Takes a localized javascript date and returns a date set to the UTC time.
1444
Date.prototype.toUTCDate = function () {
1445
var utcOffset = this.getUTCOffset(),
1446
sign = utcOffset[0],
1447
hours = parseInt(utcOffset.substr(1, 2), 10),
1448
mins = parseInt(utcOffset.substr(3, 4), 10),
1450
numSecs = (3600 * hours) + (60 * mins);
1453
numSecs = - numSecs;
1456
this.addSeconds(numSecs);
1459
Element.addMethods({
1461
* @name Event.observeOnce
1462
* @description Prototype observeOnce function, Courtesy of Kangax
1464
observeOnce: (Event.observeOnce = function (element, eventName, handler) {
1465
return Event.observe(element, eventName, function (e) {
1466
Event.stopObserving(element, eventName, arguments.callee);
1467
handler.call(element, e);
1473
* @description Determine what operating system the user is likely to be on: For use when chosing movie codecs, etc.
1474
* @returns {String} Abbreviation of the user's OS
1476
var getOS = function () {
1479
if (navigator.appVersion.indexOf("Win") !== -1) {
1482
if (navigator.appVersion.indexOf("Mac") !== -1) {
1485
if (navigator.appVersion.indexOf("X11") !== -1) {
1488
if (navigator.appVersion.indexOf("Linux") !== -1) {
1496
* @description Convert from cartesian to polar coordinates
1497
* @param {Int} x X coordinate
1498
* @param {Int} y Y coordinate
1499
* @returns {Object} Polar coordinates (r, theta) resulting from conversion
1501
Math.toPolarCoords = function (x, y) {
1502
var radians = Math.atan(y / x);
1504
if ((x > 0) && (y < 0)) {
1505
radians += (2 * Math.PI);
1510
else if ((x === 0) && (y > 0)) {
1511
radians = Math.PI / 2;
1513
else if ((x === 0) && (y < 0)) {
1514
radians = (3 * Math.PI) / 2;
1518
r : Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2)),
1519
theta : (180 / Math.PI) * radians
1524
* @description Return base-2 logarithm of the given number (Note: log_b(x) = log_c(x) / log_c(b))
1525
* @param {Number} x Number
1526
* @returns {Number} The base-2 logarithm of the input value
1528
Math.lg = function (x) {
1529
return (Math.log(x) / Math.log(2));
1532
* @fileOverview Contains the IconPicker class definition.
1533
* Syntax: Prototype, jQuery
1534
* @author <a href="mailto:keith.hughitt@gmail.com">Keith Hughitt</a>
1536
/*global IconPicker, Class, $, $A, jQuery*/
1537
var IconPicker = Class.create(
1538
/** @lends IconPicker.prototype */
1542
* @description Creates a new IconPicker
1543
* @param {String} id The identifier to use for the icon-picker dom-node
1545
initialize: function (id) {
1548
// Available Icon options
1549
this.AVAILABLE_ICON_SHAPES = $A(["circle", "square", "diamond"]);
1550
this.AVAILABLE_ICON_COLORS = $A(["red", "orange", "green", "yellow", "blue", "lightblue"]);
1552
// Keep track of which event-layer is being targeted.
1556
this._buildIconList();
1561
* @description Sets up the list of available icons to chose from
1563
_buildIconList: function () {
1564
var i, closeBtn, menu, self = this;
1565
menu = jQuery("<div id='" + this.id + "' style='display: none;'></div>");
1566
menu.append(jQuery('<div id=event-icon-menu-title><span style="vertical-align: middle">Chose an icon:</span></div><div id="event-icon-menu-body">'));
1569
this.AVAILABLE_ICON_COLORS.each(function (color) {
1570
self.AVAILABLE_ICON_SHAPES.each(function (shape) {
1571
var icon = jQuery('<img class="event-icon-menu-icon" src="images/events/small-' + color + "-" + shape + '.png" alt="' + color + '-' + shape + '">');
1572
icon.click(function () {
1573
self.focus.updateIcon(this.alt);
1574
jQuery('#event-icon-menu').fadeOut();
1578
menu.append(jQuery("<br>"));
1584
closeBtn = jQuery('<br><div style="text-align: right"><a class="event-url" href="#" style="margin-right: 2px;">[Close]</a></div>').click(function () {
1585
jQuery('#event-icon-menu').fadeOut();
1587
menu.append(closeBtn);
1588
menu.append("</div>");
1590
jQuery('body').append(menu);
1594
* @description Toggle IconPicker visibility
1595
* @param {Object} layer The EventLayer icon picker is associated with.
1596
* @param {Object} pos The mouse-click position
1598
toggle: function (layer, pos) {
1600
jQuery('#' + this.id).css({'left': pos.left + 16, 'top': pos.top + 16}).slideToggle();
1604
* @fileOverview Contains class definition for a simple layer manager
1605
* @author <a href="mailto:keith.hughitt@gmail.com">Keith Hughitt</a>
1607
/*global LayerManager, Class, UIElement, $A */
1608
var LayerManager = Class.create(UIElement,
1609
/** @lends LayerManager.prototype */
1613
* @description Creates a new LayerManager
1614
* @param {Object} A Rseference to the main application controller
1616
initialize: function (controller) {
1617
this.controller = controller;
1618
this.layers = $A([]);
1621
//hasId: function (id) {
1622
//return (this.layers.grep(id).length > 0 ? true : false);
1626
* @description Add a new layer
1628
addLayer: function (layer) {
1629
this.layers.push(layer);
1633
* @description Adds a layer that is not already displayed
1635
addNewLayer: function () {
1636
var priorityQueue, currentLayers, p, defaultChoice = "SOHEITEIT171";
1637
priorityQueue = $A([
1638
"SOHEITEIT195", "SOHLAS0C20WL", "SOHLAS0C30WL", "SOHLAS0C20WL", "SOHMDIMDImag", "SOHMDIMDIint", "SOHEITEIT171", "SOHEITEIT284", "SOHEITEIT304"
1641
// current layers in above form
1642
currentLayers = $A([]);
1643
this.tileLayers().each(function (l) {
1644
currentLayers.push(l.observatory + l.instrument + l.detector + l.measurement);
1647
// remove existing layers from queue
1648
currentLayers.each(function(id) {
1649
priorityQueue = priorityQueue.without(id);
1652
p = priorityQueue.first() || defaultLayer;
1654
this.addLayer(new TileLayer(this.controller.viewports[0], { tileAPI: this.controller.tileAPI, observatory: p.substr(0,3), instrument: p.substr(3,3), detector: p.substr(6,3), measurement: p.substr(9,3), startOpened: true }));
1655
this.refreshSavedTileLayers();
1659
* @description Gets the number of TileLayers currently loaded
1660
* @return {Integer} Number of tile layers present.
1662
numTileLayers: function () {
1664
this.layers.each(function (l) {
1665
if (l.type === "TileLayer") {
1674
* @description Returns the largest width and height of any layers (does not have to be from same layer)
1675
* @returns {Object} The width and height of the largest layer
1677
getMaxDimensions: function () {
1681
this.layers.each(function (l) {
1682
if (l.type === "TileLayer") {
1683
// Ignore if the relative dimensions haven't been retrieved yet
1684
if (Object.isNumber(l.relWidth)) {
1685
maxWidth = Math.max(maxWidth, l.relWidth);
1686
maxHeight = Math.max(maxHeight, l.relHeight);
1691
//console.log("Max dimensions: " + maxWidth + ", " + maxHeight);
1693
return {width: maxWidth, height: maxHeight};
1697
* @description Checks for presence of a specific event catalog
1698
* @param {String} catalog Catalog ID
1700
hasEventCatalog: function (catalog) {
1701
return (this.eventLayers().find(function (l) {
1702
return l.catalog === catalog;
1707
* @description Returns only event-layers.
1708
* @returns {Array} An array containing each of the currently displayed EVENT layers
1710
eventLayers: function () {
1711
return this.layers.findAll(function (l) {
1712
return l.type === "EventLayer";
1717
* @description Returns only tile-layers.
1718
* @returns {Array} An array containing each of the currently displayed TILE layers
1720
tileLayers: function () {
1721
return this.layers.findAll(function (l) {
1722
return l.type === "TileLayer";
1727
* @description Gets the number of event layers currently loaded
1728
* @return {Integer} Number of event layers present.
1730
numEventLayers: function () {
1732
this.layers.each(function (l) {
1733
if (l.type === "EventLayer") {
1742
* @description Removes a layer
1743
* @param {Object} The layer to remove
1745
removeLayer: function (layer) {
1746
layer.domNode.remove();
1747
this.layers = this.layers.without(layer);
1751
* @description Reload layers
1753
reloadLayers: function () {
1754
this.layers.each(function (layer) {
1760
* @description Reloads each of the tile layers
1762
resetLayers: function (visible) {
1763
this.layers.each(function (layer) {
1764
layer.reset(visible);
1769
* @description Updates the list of loaded tile layers stored in cookies
1771
refreshSavedTileLayers: function () {
1772
//console.log("refreshSavedTileLayers");
1773
var tilelayers = [];
1775
this.tileLayers().each(function (layer) {
1777
tileAPI : layer.tileAPI,
1778
observatory : layer.observatory,
1779
instrument : layer.instrument,
1780
detector : layer.detector,
1781
measurement : layer.measurement
1784
tilelayers.push(settings);
1787
this.controller.userSettings.set('tile-layers', tilelayers);
1791
* @fileOverview Contains the "MessageConsole" class definition.
1792
* @author <a href="mailto:keith.hughitt@gmail.com">Keith Hughitt</a>
1794
/*global document, UIElement, Effect, $, Class, Element, Event, window */
1795
var MessageConsole = Class.create(UIElement ,
1796
/** @lends MessageConsole.prototype */
1800
* @description Creates a new MessageConsole.<br><br>
1801
* MessageConsole Provides a mechanism for displayed useful information to the user.
1802
* For ease of use, the class provides methods comparable to Firebug's for outputting
1803
* messages of different natures: "log" for generic unstyled messages, or for debbuging
1804
* use, "info" to inform the user of some interesting change or event, and "warning" and
1805
* "error" for getting the user's attention.
1806
* @param {Object} controller A reference to the Helioviewer (controller)
1807
* @param {String} container The id of the container for messages to be displayed in
1808
* @param {String} viewport The id of the viewport container
1810
initialize: function (controller, container, viewport) {
1811
this.controller = controller;
1812
this.console = $(container);
1813
this.viewportId = viewport;
1817
* @description Logs a message to the message-console
1818
* @param {String} msg Message to display
1820
log: function (msg) {
1821
this.console.update(new Element('p', {style: 'color: #6495ED; font-weight: bold;'}).insert(msg));
1823
trash = new Effect.Appear(this.console, { duration: 3.0 });
1825
//Hide the message after several seconds have passed
1826
window.setTimeout(function () {
1827
var trash = new Effect.Fade(self.console, { duration: 3.0 });
1831
//info: function (msg) {
1835
* @description Displays a warning message in the message console
1836
* @param {String} msg Message to display
1838
warn: function (msg) {
1839
this.console.update(new Element('p', {style: 'color: yellow; font-weight: bolder;'}).insert(msg));
1841
trash = new Effect.Appear(this.console, { duration: 3.0 });
1843
//Hide the message after several seconds have passed
1844
window.setTimeout(function () {
1845
var trash = new Effect.Fade(self.console, { duration: 3.0 });
1850
* @description Displays an error message in the message console
1851
* @param {String} msg Message to display
1853
error: function (msg) {
1854
this.console.update(new Element('p', {style: 'color: red'}).insert(msg));
1856
trash = new Effect.Shake(this.viewportId, {distance: 15, duration: 0.1});
1857
trash = new Effect.Appear(this.console, { duration: 3.0 });
1859
//Hide the message after several seconds have passed
1860
window.setTimeout(function () {
1861
var trash = new Effect.Fade(self.console, { duration: 3.0 });
1866
* @description Displays message along with a hyperlink in the message console
1867
* @param {String} msg Message to display
1868
* @param {String} Hyperlink text (Note: Event-handler should be used to handle hyperlink clicks. The link address thus is set to "#")
1870
link: function (msg, linkText) {
1872
linkId, wrapper, link, trash;
1874
// Generate a temporary id
1875
linkId = 'link-' + this.controller.date.getTime() / 1000;
1878
wrapper = new Element('span');
1879
link = new Element('a', {href: '#', id: linkId, 'class': 'message-console-link'}).update(linkText);
1880
wrapper.insert(msg);
1881
wrapper.insert(link);
1883
this.console.update(new Element('p', {style: 'color: #6495ED;'}).insert(wrapper));
1884
trash = new Effect.Appear(this.console, { duration: 2.0 });
1886
//For downloads, leave the message up until the user clicks on the link provided.
1887
//Note: another possibility is to add a "close" option.
1888
Event.observe(linkId, 'click', function () {
1889
self.console.hide();
1895
* @fileoverview Contains the definition of a class for generating and displaying movies.
1896
* @author <a href="mailto:keith.hughitt@gmail.com">Keith Hughitt</a>
1899
/*global MoviesBuilder, Class, UIElement, Event, document, window, Shadowbox, getOS, Ajax */
1900
//TODO: pass in bit-rate depending upon codec chosen! Xvid?
1902
var MovieBuilder = Class.create(UIElement,
1903
/** @lends MovieBuilder.prototype */
1906
* @description Default MovieBuilder options
1910
url : "api/index.php",
1911
minZoomLevel: 13, //can relax for single layer movies...
1915
edgeEnhance : false,
1916
format : {win: "asf", mac: "mov", linux: "mp4"}
1921
* @description Creates a new MovieBuilder
1922
* @param {Object} options Custom MovieBuilder options
1924
initialize: function (options) {
1925
Object.extend(this, this.defaultOptions);
1926
Object.extend(this, options);
1930
//Quick Movie Event Handler
1931
Event.observe(this.id, 'click', function () {
1933
var hv = self.controller,
1934
hqFormat, displayRange, xhr;
1938
// Chose an optimal codec based on User's OS
1939
hqFormat = self.format[getOS()];
1941
// Get range of tiles to use
1942
displayRange = hv.viewports[0].displayRange();
1945
xhr = new Ajax.Request(self.url, {
1948
action: "buildQuickMovie",
1949
layers: "SOHEITEIT304,SOHLAS0C20WL",
1950
startDate: hv.date.getTime() / 1000,
1951
zoomLevel: hv.viewports[0].zoomLevel, //Math.max(hv.viewports[0].zoomLevel, self.minZoomLevel),
1952
numFrames: self.numFrames,
1953
frameRate: self.frameRate,
1954
edges: self.edgeEnhance,
1955
sharpen: self.sharpen,
1957
xRange: displayRange.xStart + ", " + displayRange.xEnd,
1958
yRange: displayRange.yStart + ", " + displayRange.yEnd
1960
onComplete: function (transport) {
1961
// Let user know that video is read
1962
var linkId = self.controller.messageConsole.link('', 'Quick-Video ready! Click here to start watching.');
1964
self.active = false;
1966
Event.observe(linkId, 'click', function () {
1969
title: 'Helioviewer Movie Player',
1972
content: self.url + '?action=playMovie&format=' + hqFormat + '&url=' + transport.responseJSON
1982
* @fileOverview Contains the class definition for an TileLayer class.
1983
* @author <a href="mailto:keith.hughitt@gmail.com">Keith Hughitt</a>
1984
* @author <a href="mailto:patrick.schmiedel@gmx.net">Patrick Schmiedel</a>
1985
* @see TileLayerAccordion, Layer
1987
* Syntax: jQuery, Prototype
1989
/*global TileLayer, Class, Layer, Ajax, Event, $, Element, Image */
1990
var TileLayer = Class.create(Layer,
1991
/** @lends TileLayer.prototype */
1994
* @description Default TileLayer options
2008
* @description Creates a new TileLayer
2009
* @param {Object} viewport Viewport to place the tiles in
2011
* <br><div style='font-size:16px'>Options:</div><br>
2012
* <div style='margin-left:15px'>
2013
* <b>type</b> - The type of the layer (used by layer manager to differentiate event vs. tile layers)<br>
2014
* <b>tileSize</b> - Tilesize to use<br>
2015
* <b>source</b> - Tile source ["database" | "filesystem"]<br>
2016
* <b>rootDir</b> - The root directory where the tiles are stored (when using filesystem as the tile source)<br>
2017
* <b>opacity</b> - Default opacity (adjusted automatically when layer is added)<br>
2018
* <b>autoOpaicty</b> - Whether or not the opacity should be automatically determined when the image properties are loaded<br>
2019
* <b>startOpened</b> - Whether or not the layer menu entry should initially be open or closed<br>
2022
initialize: function (viewport, options) {
2023
Object.extend(this, this.defaultOptions);
2024
Object.extend(this, options);
2025
this.viewport = viewport;
2027
this.tileSize = viewport.tileSize;
2029
this.layerManager = viewport.controller.layerManager;
2030
this.id = 'tilelayer' + new Date().getTime();
2032
// Create an HTML container to hold the layer tiles
2033
this.domNode = new Element('div', {className: 'tile-layer-container', style: 'position: absolute;'});
2034
viewport.movingContainer.appendChild(this.domNode);
2036
this.viewport.addObserver('move', this.viewportMove.bind(this));
2039
this.loadClosestImage();
2043
* @description Refreshes the TileLayer
2045
reload: function () {
2046
this.loadClosestImage();
2050
* @function Remove TileLayer tiles
2052
removeTiles: function () {
2057
* @description Reload the tile layer
2058
* @param {Object} A 2-d binary array indicating which tiles have been (should be) loaded
2060
reset: function (visible) {
2061
var i, j, currentScale, scaleOffset, old, numTiles, numTilesLoaded, indices, tile, onLoadComplete, self = this;
2063
// Start loading indicator
2064
this.viewport.controller.loadingIndicator.loadingStarted();
2066
// Update relevant dimensions
2067
//zoomOffset = this.lowestRegularZoom - this.viewport.zoomLevel;
2068
currentScale = this.viewport.controller.baseScale * Math.pow(2, this.viewport.zoomLevel - this.viewport.controller.baseZoom);
2069
scaleOffset = this.naturalImageScale / currentScale;
2071
this.relWidth = this.width * scaleOffset;
2072
this.relHeight = this.height * scaleOffset;
2074
//console.log("relative Width & Height: " + this.relWidth + " , " + this.relHeight);
2076
// Let user know if the requested zoom-level is lower than the lowest level natively supported
2077
if ((this.viewport.zoomLevel < this.minZoom) && (this.viewport.controller.userSettings.get('warn-zoom-level') === "false")) {
2078
this.viewport.controller.messageConsole.log("Note: " + this.name + " is not available at this resolution. Images will be artificially enlarged.");
2079
this.viewport.controller.userSettings.set('warn-zoom-level', true);
2082
// Remove tiles in cache
2085
this.refreshUTCDate();
2087
// Reference old tile nodes to remove after new ones are done loading
2089
this.domNode.childElements().each(function (tile) {
2093
//TODO: Determine range to check
2097
indices = this.viewport.visibleRange;
2099
onLoadComplete = function (e) {
2100
numTilesLoaded += 1;
2101
if (numTilesLoaded === numTiles) {
2102
//console.log("Finished loading ALL images! (" + numTiles + ") total.");
2103
old.each(function (tile) {
2104
//tile.parentNode && tile.remove();
2105
if (tile.parentNode) {
2109
self.viewport.controller.loadingIndicator.loadingFinished();
2113
for (i = indices.xStart; i <= indices.xEnd; i += 1) {
2114
for (j = indices.yStart; j <= indices.yEnd; j += 1) {
2115
if (visible[i][j]) {
2116
tile = $(this.domNode.appendChild(this.getTile(i, j, this.viewport.zoomLevel)));
2118
if (!this.tiles[i]) {
2122
this.tiles[i][j] = {};
2123
this.tiles[i][j].img = tile;
2127
// Makes sure all of the images have finished downloading before swapping them in
2128
Event.observe(this.tiles[i][j].img, 'load', onLoadComplete);
2135
* @description Update TileLayer date
2137
refreshUTCDate: function () {
2138
var date = new Date(this.timestamp * 1000);
2140
this.utcDate = date;
2144
* @description Store retrieved image properties
2145
* @param {Object} imageProperties Properties of the image associated with the TileLayer
2147
setImageProperties: function (imageProperties) {
2148
//Only load image if it is different form what is currently displayed
2149
if (imageProperties.imageId === this.imageId) {
2150
this.fire('obs_time_change', this);
2154
Object.extend(this, imageProperties);
2156
this.fire('obs_time_change', this);
2158
//IE7: Want z-indices < 1 to ensure event icon visibility
2159
this.setZIndex(parseInt(this.opacityGroupId, 10) - 10);
2161
//handle opacities for any overlapping images
2162
if (this.autoOpacity) {
2163
this.setInitialOpacity();
2164
this.autoOpacity = false;
2167
// Let others know layer has been added
2168
this.fire('change', this);
2170
this.viewport.checkTiles(true);
2172
this.reset(this.viewport.visible);
2176
* @description Associates an image with the TileLayer and fetches some meta information relating to that image
2177
* @param {String} imageId The identifier of the image to be tiled
2179
setImage: function (imageId) {
2180
if (imageId === this.imageId) {
2183
this.imageId = imageId;
2184
this.loadImageProperties();
2185
this.reset(this.viewport.visible);
2189
* @description Sets the opacity for the layer, taking into account layers which overlap one another.
2191
setInitialOpacity: function () {
2196
//Note: No longer adjust other layer's opacities... only the new layer's (don't want to overide user settings).
2197
this.layerManager.layers.each(function (layer) {
2198
if (parseInt(layer.opacityGroupId, 10) === parseInt(self.opacityGroupId, 10)) {
2203
//Do no need to adjust opacity if there is only one image
2205
opacity = opacity / counter;
2206
this.domNode.setOpacity(opacity);
2207
this.opacity = opacity * 100;
2211
this.layerManager.layers.each (function (layer) {
2212
if (parseInt(layer.opacityGroupId) == parseInt(self.opacityGroupId)) {
2215
//Do no need to adjust opacity of the first image
2217
opacity = opacity / counter;
2218
layer.domNode.setOpacity(opacity);
2219
layer.opacity = opacity * 100;
2220
//layer.domNode.setStyle({'filter': 'alpha(opacity=' + (opacity * 100) + ')', width: self.tileSize});
2227
* @description Update the tile layer's opacity
2228
* @param {int} Percent opacity to use
2230
setOpacity: function (opacity) {
2231
this.opacity = opacity;
2232
opacity = opacity / 100;
2233
this.domNode.setOpacity(opacity);
2234
//this.domNode.setStyle({'filter': 'alpha(opacity=' + (opacity * 100) + ')'});
2238
* @description Loads the closest image in time to that requested
2240
loadClosestImage: function () {
2241
var date = this.viewport.controller.date,
2242
processResponse, xhr;
2245
processResponse = function (transport) {
2246
this.setImageProperties(transport.responseJSON);
2248
var hv = this.viewport.controller;
2250
// update viewport sandbox if necessary
2251
this.viewport.updateSandbox();
2253
// Add to tileLayer Accordion if it's not already there
2254
if (!hv.tileLayerAccordion.hasId(this.id)) {
2255
hv.tileLayerAccordion.addLayer(this);
2257
// Otherwise update the accordion entry information
2259
hv.tileLayerAccordion.updateTimeStamp(this);
2260
hv.tileLayerAccordion.updateLayerDesc(this.id, this.name);
2261
hv.tileLayerAccordion.updateOpacitySlider(this.id, this.opacity);
2266
xhr = new Ajax.Request(this.viewport.controller.imageAPI, {
2269
action: 'getClosestImage',
2270
observatory: this.observatory,
2271
instrument: this.instrument,
2272
detector: this.detector,
2273
measurement: this.measurement,
2274
timestamp: date.getTime() / 1000,
2277
onSuccess: processResponse.bind(this)
2282
* @description Toggle image sharpening
2284
toggleSharpening: function () {
2285
if (this.sharpen === true) {
2288
//jQuery(this.domNode.childElements());
2289
//jQuery("img.tile[src!=images/transparent_512.gif]").pixastic("sharpen", {amount: 0.35});
2291
this.sharpen = !this.sharpen;
2295
* @description Check to see if all visible tiles have been loaded
2296
* @param {Object} position Position
2298
viewportMove: function (position) {
2299
var visible = this.viewport.visible,
2300
indices = this.viewport.visibleRange,
2303
//console.log("Checking tiles from " + indices.xStart + " to " + indices.xEnd);
2305
for (i = indices.xStart; i <= indices.xEnd; i += 1) {
2306
for (j = indices.yStart; j <= indices.yEnd; j += 1) {
2307
if (!this.tiles[i]) {
2310
if (visible[i][j] && (!this.tiles[i][j])) {
2311
//console.log("Loading new tile");
2312
this.tiles[i][j] = $(this.domNode.appendChild(this.getTile(i, j, this.viewport.zoomLevel)));
2319
* @description Generates URL to retrieve a single Tile and displays the transparent tile if request fails
2320
* @param {Int} x Tile X-coordinate
2321
* @param {Int} y Tile Y-coordinate
2322
* @returns {String} URL to retrieve the requested tile
2324
getTile: function (x, y) {
2325
var left = x * this.tileSize,
2326
top = y * this.tileSize,
2327
zoom = this.viewport.zoomLevel,
2333
img = $(new Image());
2334
img.addClassName('tile');
2339
img.unselectable = 'on';
2341
img.onmousedown = rf;
2343
img.onmouseover = rf;
2344
img.oncontextmenu = rf;
2345
img.galleryimg = 'no';
2349
// If tile doesn't exist, load the transparent tile in it's place
2350
Event.observe(img, 'error', function () {
2351
this.src = 'images/transparent_' + ts + '.gif';
2355
img.src = this.tileAPI + '?action=getTile&x=' + x + '&y=' + y + '&zoom=' + zoom + '&imageId=' + this.imageId + '&ts=' + ts;
2361
* @fileOverview Contains the class definition for an TileLayerAccordion class.
2362
* @author <a href="mailto:keith.hughitt@gmail.com">Keith Hughitt</a>
2363
* @see LayerManager, TileLayer
2364
* @requires ui.dynaccordion.js
2365
* Syntax: jQuery, Prototype
2367
/*global TileLayerAccordion, Class, jQuery, Ajax, Layer, $, $$, $A, $R, Control, Element, TileLayer, Event, Hash */
2368
var TileLayerAccordion = Class.create(Layer,
2369
/** @lends TileLayerAccordion.prototype */
2373
* @description Creates a new TileLayerAccordion
2374
* @param {Object} layerManager Reference to the application layer manager
2375
* @param {String} containerId ID for the outermost continer where the layer manager user interface should be constructed
2377
initialize: function (layerManager, containerId) {
2378
this.layerManager = layerManager;
2379
this.container = jQuery('#' + containerId);
2380
this.queryURL = "api/index.php";
2384
//Setup menu UI components
2387
//Initialize accordion
2388
this.domNode = jQuery('#TileLayerAccordion-Container');
2389
this.domNode.dynaccordion({startClosed: true});
2391
//Individual layer menus
2392
this.layerSettings = new Hash();
2396
* @description Adds a new entry to the tile layer accordion
2397
* @param {Object} layer The new layer to add
2399
addLayer: function (layer) {
2400
// Determine what measurements to display
2401
var processResponse = function (transport) {
2402
// Create accordion entry header
2403
//var collapseIcon = "<img class='collapse' id='collapse-" + layer.id + "' src='images/blackGlass/accordion_menu_closed.png'></button>";
2404
var visibilityBtn, removeBtn, head, body, slider;
2406
visibilityBtn = "<button class='layerManagerBtn visible' id='visibilityBtn-" + layer.id + "' value=true type=button title='toggle layer visibility'></button>";
2407
removeBtn = "<button class='layerManagerBtn remove' id='removeBtn-" + layer.id + "' type=button title='remove layer'></button>";
2408
head = "<div class=layer-Head><span class=tile-accordion-header-left>" + layer.name + "</span><span class=tile-accordion-header-right><span class=timestamp></span> |" + visibilityBtn + removeBtn + "</span></div>";
2410
// Update allowable choices
2411
this.options.observatories = transport.responseJSON.observatories;
2412
this.options.instruments = transport.responseJSON.instruments;
2413
this.options.detectors = transport.responseJSON.detectors;
2414
this.options.measurements = transport.responseJSON.measurements;
2416
// Create accordion entry body
2417
body = this._buildEntryBody(layer);
2419
//var startOpened = (this.layerManager.numTileLayers() > 1);
2422
this.domNode.dynaccordion("addSection", {
2426
open: layer.startOpened
2430
slider = new Control.Slider("opacity-slider-handle-" + layer.id, "opacity-slider-track-" + layer.id, {
2431
sliderValue: layer.opacity,
2434
onSlide: function (v) {
2435
layer.setOpacity(v);
2439
/* NOTE: Bug in jQuery slider currently prevents it from working properly when
2440
* initialized hidden. See http://groups.google.com/group/jquery-ui/browse_thread/thread/5febf768db177780.
2441
jQuery("#opacity-slider-track-" + layer.id).slider({
2442
startValue: layer.opacity,
2443
change: function(e, ui) {
2445
layer.setOpacity(val);
2449
// Keep a reference to the dom-node
2450
//this.menuEntries.push({id: layer.id, header: head, cell: body});
2451
this.layerSettings.set(layer.id, {
2454
opacitySlider: slider
2458
this._setupEventHandlers(layer);
2461
this.updateTimeStamp(layer);
2465
xhr = new Ajax.Request(this.queryURL, {
2467
onSuccess: processResponse.bind(this),
2469
action : "getLayerAvailability",
2470
observatory: layer.observatory,
2471
instrument: layer.instrument,
2472
detector: layer.detector,
2473
measurement: layer.measurement,
2480
* @description Checks to see if the given layer is listed in the accordion
2481
* @param {String} id ID of the layer being checked
2483
hasId: function (id) {
2484
return (this.layerSettings.keys().grep(id).length > 0 ? true : false);
2488
* @description Builds the body section of a single TileLayerAccordion entry. NOTE: width and height must be hardcoded for slider to function properly.
2489
* @param {Object} layer The new layer to add
2490
* @see <a href="http://groups.google.com/group/Prototypejs/browse_thread/thread/60a2676a0d62cf4f">This discussion thread</a> for explanation.
2492
_buildEntryBody: function (layer) {
2493
var id, options, opacitySlide, obs, inst, det, meas, fits;
2496
options = this.options;
2498
// Opacity slider placeholder
2499
opacitySlide = "<div class='layer-select-label'>Opacity: </div>";
2500
opacitySlide += "<div class='opacity-slider-track' id='opacity-slider-track-" + id + "' style='width:120px; height:10px;'>";
2501
opacitySlide += "<div class='opacity-slider-handle' id='opacity-slider-handle-" + id + "' style='10px; 19px;'></div>";
2502
opacitySlide += "</div>";
2504
// Populate list of available observatories
2505
obs = "<div class=layer-select-label>Observatory: </div> ";
2506
obs += "<select name=observatory class=layer-select id='observatory-select-" + id + "'>";
2507
jQuery.each(options.observatories, function (i, o) {
2508
obs += "<option value='" + o.abbreviation + "'";
2509
if (layer.observatory === o.abbreviation) {
2510
obs += " selected='selected'";
2512
obs += ">" + o.name + "</option>";
2514
obs += "</select><br>";
2516
// Populate list of available instruments
2517
inst = "<div class=layer-select-label>Instrument: </div> ";
2518
inst += "<select name=instrument class=layer-select id='instrument-select-" + id + "'>";
2519
jQuery.each(options.instruments, function (i, o) {
2520
inst += "<option value='" + o.abbreviation + "'";
2521
if (layer.instrument === o.abbreviation) {
2522
inst += " selected='selected'";
2524
inst += ">" + o.name + "</option>";
2526
inst += "</select><br>";
2528
// Populate list of available Detectors
2529
det = "<div class=layer-select-label>Detector: </div> ";
2530
det += "<select name=detector class=layer-select id='detector-select-" + id + "'>";
2531
jQuery.each(options.detectors, function (i, o) {
2532
det += "<option value='" + o.abbreviation + "'";
2533
if (layer.detector === o.abbreviation) {
2534
det += " selected='selected'";
2536
det += ">" + (o.name === "" ? o.abbreviation : o.name) + "</option>";
2538
det += "</select><br>";
2540
// Populate list of available Detectors
2541
meas = "<div class=layer-select-label>Measurement: </div> ";
2542
meas += "<select name=measurement class=layer-select id='measurement-select-" + id + "'>";
2543
jQuery.each(options.measurements, function (i, o) {
2544
meas += "<option value='" + o.abbreviation + "'";
2545
if (layer.measurement === o.abbreviation) {
2546
meas += " selected='selected'";
2548
meas += ">" + o.name + "</option>";
2550
meas += "</select><br><br>";
2552
fits = "<a href='#' id='showFITSBtn-" + id + "' style='margin-left:160px; color: white; text-decoration: none;'>FITS Header</a><br>";
2554
return (opacitySlide + obs + inst + det + meas + fits);
2557
//_addOpacitySlider: function (layer) {
2562
* @description Makes sure the slider is set to the right value
2563
* @param {Object} id ID of the TileLayer whose opacity should be adjusted
2564
* @param {Object} opacity The new opacity value
2566
updateOpacitySlider: function (id, opacity) {
2567
this.layerSettings.get(id).opacitySlider.setValue(opacity);
2571
* @description Handles setting up an empty tile layer accordion.
2573
_setupUI: function () {
2574
var title, addLayerBtn, hv, self = this;
2576
// Create a top-level header and an "add layer" button
2577
title = jQuery('<span class="accordion-title">Overlays</span>').css({'float': 'left'});
2578
addLayerBtn = jQuery('<a href=# class=gray>[Add]</a>').css({'margin-right': '14px'});
2579
this.container.append(jQuery('<div></div>').css('text-align', 'right').append(title).append(addLayerBtn));
2580
this.container.append(jQuery('<div id="TileLayerAccordion-Container"></div>'));
2583
hv = this.layerManager.controller;
2584
addLayerBtn.click(function () {
2585
self.layerManager.addNewLayer();
2590
* @description Sets up event-handlers for a TileLayerAccordion entry
2591
* @param {Object} layer The layer being added
2593
_setupEventHandlers: function (layer) {
2594
var toggleVisibility, removeLayer, showFITS, visible, icon, accordion, dialogId, processResponse, response, formatted, xhr, self = this,
2595
visibilityBtn = jQuery("#visibilityBtn-" + layer.id),
2596
removeBtn = jQuery("#removeBtn-" + layer.id),
2597
fitsBtn = jQuery("#showFITSBtn-" + layer.id);
2599
// Function for toggling layer visibility
2600
toggleVisibility = function (e) {
2601
visible = layer.toggleVisible();
2602
icon = (visible ? 'LayerManagerButton_Visibility_Visible.png' : 'LayerManagerButton_Visibility_Hidden.png');
2603
jQuery("#visibilityBtn-" + layer.id).css('background', 'url(images/blackGlass/' + icon + ')');
2604
e.stopPropagation();
2607
// Function for handling layer remove button
2608
removeLayer = function (e) {
2610
accordion.layerManager.removeLayer(layer);
2611
accordion.domNode.dynaccordion('removeSection', {id: layer.id});
2612
accordion.layerSettings.unset(layer.id);
2613
accordion.layerManager.refreshSavedTileLayers();
2615
//accordion.layers = accordion.layers.without(layer.id);
2616
e.stopPropagation();
2619
// Function for handling requests to display FITS header Info
2620
showFITS = function () {
2621
dialogId = "fits-header-" + layer.id;
2623
// Check to see if a dialog already exists
2624
if (jQuery("#" + dialogId).length === 0) {
2627
processResponse = function (transport) {
2628
response = transport.responseJSON;
2631
formatted = "<div id='" + dialogId + "' style='overflow: auto; padding:0px'><div style='background: white; color: black; padding:1.5em 1.7em'>";
2632
$A(response).each(function (line) {
2633
formatted += line + "<br>";
2635
formatted += "</div></div>";
2637
jQuery("body").append(formatted);
2638
jQuery("#" + dialogId).dialog({
2640
title: "FITS Header: " + layer.name,
2648
xhr = new Ajax.Request("api/index.php", {
2650
onSuccess: processResponse.bind(this),
2652
action: "getJP2Header",
2653
imageId: layer.imageId
2657
// If it does exist but is closed, open the dialog
2659
if (!jQuery("#" + dialogId).dialog("isOpen")) {
2660
jQuery("#" + dialogId).dialog("open");
2662
//jQuery("#" + dialogId).dialog("destroy");
2663
jQuery("#" + dialogId).dialog("close");
2668
// Event handlers for select items
2669
jQuery.each(jQuery('#' + layer.id + ' > div > select'), function (i, item) {
2670
jQuery(item).change(function (e) {
2671
//alert(this.name + "= " + this.value);
2672
if (this.name === "observatory") {
2673
layer.observatory = this.value;
2675
else if (this.name === "instrument") {
2676
layer.instrument = this.value;
2678
else if (this.name === "detector") {
2679
layer.detector = this.value;
2681
else if (this.name === "measurement") {
2682
layer.measurement = this.value;
2685
// Validate new settings and reload layer
2686
self._onLayerSelectChange(layer, this.name, this.value);
2690
// Display FITS header
2691
fitsBtn.bind('click', this, showFITS);
2693
//visibilityBtn.click(toggleVisibility);
2694
visibilityBtn.bind('click', this, toggleVisibility);
2695
removeBtn.bind('click', this, removeLayer);
2699
* @description Checks to make sure the new layer settings are valid. If the new combination of
2700
* choices are not compatable, change values to right of most-recently changed parameter to valid
2701
* settings. Once the combination is acceptable, reload the tile layer.
2702
* @param {TileLayer} layer The layer to which the changes have been made
2703
* @param {String} changed The field altered
2704
* @param {String} The new value chosen
2706
_onLayerSelectChange: function (layer, changed, value) {
2707
var obs, inst, det, meas, xhr, processResponse;
2709
// Ajax callback function
2710
processResponse = function (transport) {
2712
this.options = transport.responseJSON;
2714
// Case 1: Observatory changed
2715
if (changed === "observatory") {
2716
this._updateOptions(layer.id, "instrument", this.options.instruments);
2718
//Make sure the instrument choice is still valid.
2719
if ($A(this.options.instruments).grep(layer.instrument).length === 0) {
2720
layer.instrument = this.options.instruments[0];
2724
// Case 2: Instrument changed
2725
if ((changed === "observatory") || (changed === "instrument")) {
2726
this._updateOptions(layer.id, "detector", this.options.detectors);
2728
//Make sure the detector choice is still valid.
2729
if (!$A(this.options.detectors).find(function (det) {
2730
return det.abbreviation === layer.detector;
2732
layer.detector = this.options.detectors[0].abbreviation;
2736
// Case 3: Detector changed
2737
if ((changed === "observatory") || (changed === "instrument") || (changed === "detector")) {
2738
this._updateOptions(layer.id, "measurement", this.options.measurements);
2740
//Make sure the measurement choice is still valid.
2741
if (!$A(this.options.measurements).find(function (meas) {
2742
return meas.abbreviation === layer.measurement;
2744
layer.measurement = this.options.measurements[0].abbreviation;
2748
//if ($A(this.options.measurements).grep(layer.measurement).length == 0) {
2749
// layer.measurement = this.options.measurements[0];
2752
var instVal = $F('instrument-select-' + layer.id);
2753
if ($A(this.options.instruments).grep(instVal).length == 0) {
2754
layer.instrument = this.options.instruments[0];
2756
//update selectedIndex
2758
$$('#instrument-select-' + layer.id + ' > option').each(function(opt, i) {
2759
if (opt.value === self.options.instruments[0]) {
2760
$('instrument-select-' + layer.id).selectedIndex = i;
2766
//reload layer settings
2769
// Update stored user settings
2770
this.layerManager.refreshSavedTileLayers();
2773
// Do not need to update options if the measurement is changed
2774
if (changed !== "measurement") {
2775
// Update SELECT options
2776
obs = (changed === "observatory" ? value : layer.observatory);
2777
inst = (changed === "instrument" ? value : layer.instrument);
2778
det = (changed === "detector" ? value : layer.detector);
2779
meas = (changed === "measurement" ? value : layer.measurement);
2782
xhr = new Ajax.Request(this.queryURL, {
2784
onSuccess: processResponse.bind(this),
2786
action: "getLayerAvailability",
2798
//reload layer settings
2801
// Update stored user settings
2802
this.layerManager.refreshSavedTileLayers();
2807
* @description Updates options for a single SELECT element.
2808
* @param {String} id The ID of the layer whose parameters were adjusted
2809
* @param {String} field The field to adjust
2810
* @param {Array} newOptions updated choices
2812
_updateOptions: function (id, field, newOptions) {
2815
//Remove old options
2816
$$('#' + field + '-select-' + id + ' > option').each(function (o) {
2821
select = $(field + '-select-' + id);
2822
$A(newOptions).each(function (o) {
2823
opt = new Element('option', {value: o.abbreviation}).insert(o.name === "" ? o.abbreviation : o.name);
2830
* @description Updates the displayed timestamp for a given tile layer
2831
* @param {Object} layer The layer being updated
2833
updateTimeStamp: function (layer) {
2834
var domNode, date, dateString, timeDiff, ts;
2836
//Grab timestamp dom-node
2837
domNode = $(layer.id).select('.timestamp').first();
2839
//remove any pre-existing styling
2840
domNode.removeClassName("timeBehind");
2841
domNode.removeClassName("timeAhead");
2842
domNode.removeClassName("timeSignificantlyOff");
2844
// Update the timestamp
2845
date = new Date(layer.timestamp * 1000);
2846
dateString = date.toYmdUTCString() + ' ' + date.toHmUTCString();
2848
// Calc the time difference
2849
timeDiff = layer.timestamp - this.layerManager.controller.date.getTime() / 1000;
2851
//this.domNode.select(".timestamp").first().update(dateString + ' ' + timeDiffStr);
2852
domNode.update(dateString);
2854
//get timestep (TODO: create a better accessor)
2855
//var ts = this.layerManager.controller.timeStepSlider.timestep.numSecs;
2856
ts = this.layerManager.controller.timeIncrementSecs;
2858
// Check to see if observation times match the actual time
2860
if (Math.abs(timeDiff) > (4 * ts)) {
2861
domNode.addClassName("timeSignificantlyOff");
2864
domNode.addClassName("timeBehind");
2867
else if (timeDiff > 0) {
2868
if (timeDiff > (4 * ts)) {
2869
domNode.addClassName("timeSignificantlyOff");
2872
domNode.addClassName("timeAhead");
2878
* @description Updates the description for a given tile layer
2879
* @param {String} id Layer id
2880
* @param {String} desc New description to use
2882
updateLayerDesc: function (id, desc) {
2883
$(id).select("span.tile-accordion-header-left").first().update(desc);
2888
* @fileOverview Contains the class definition for an TimeControls class.
2890
* @author <a href="mailto:keith.hughitt@gmail.com">Keith Hughitt</a>
2892
/*global TimeControls, Class, UIElement, Event, Element, $, $A */
2893
var TimeControls = Class.create(UIElement,
2894
/** @lends TimeControls.prototype */
2898
* @description Creates a new TimeControl component
2899
* @param {Object} controller Reference to the Helioviewer application class/controller
2900
* @param {String} incrementSelect ID for the HTML element for selecting the time increment
2901
* @param {String} backBtn ID for the time "Back" button
2902
* @param {String} forwardBtn ID for the time "Forward" button
2903
* @param {Int} timeIncrement The amount of time to jump, in seconds, each time the back button or forward button is pressed
2905
initialize : function (controller, incrementSelect, backBtn, forwardBtn, timeIncrement) {
2906
this.controller = controller;
2908
//Private member variables
2909
this.className = "TimeControls";
2912
this.timeIncrement = timeIncrement;
2914
// Populate select box
2915
this.addTimeIncrements(incrementSelect);
2918
Event.observe(backBtn, 'click', this.timePrevious.bind(this));
2919
Event.observe(forwardBtn, 'click', this.timeNext.bind(this));
2923
* @description Populates the time increment select item
2924
* @param {String} selectId The ID for the SELECT form item associated with the desired time increment
2926
addTimeIncrements: function (selectId) {
2927
var timeSteps, select, opt;
2930
{numSecs: 1, txt: "1 Sec"},
2931
{numSecs: 60, txt: "1 Min"},
2932
{numSecs: 300, txt: "5 Mins"},
2933
{numSecs: 900, txt: "15 Mins"},
2934
{numSecs: 3600, txt: "1 Hour"},
2935
{numSecs: 21600, txt: "6 Hours"},
2936
{numSecs: 43200, txt: "12 Hours"},
2937
{numSecs: 86400, txt: "1 Day"},
2938
{numSecs: 604800, txt: "1 Week"},
2939
{numSecs: 2419200, txt: "28 Days"}
2942
select = $(selectId);
2944
// Add time-steps to the select menu
2945
$A(timeSteps).each(function (o) {
2946
opt = new Element('option', {value: o.numSecs}).insert(o.txt);
2950
// Select default timestep
2951
select.select('option[value=' + this.timeIncrement + ']')[0].writeAttribute('selected', 'selected');
2954
Event.observe(select, 'change', this.onChange.bindAsEventListener(this));
2958
* @description Time-incremenet change event handler
2959
* @param {Event} e Prototype Event Object
2961
onChange: function (e) {
2962
this.timeIncrement = parseInt(e.target.value, 10);
2963
this.fire('timeIncrementChange', this.timeIncrement);
2967
* @description Move back one time incremement
2969
timePrevious: function () {
2970
var newDate = this.controller.date.addSeconds(-this.timeIncrement);
2971
this.controller.setDate(newDate);
2972
this.controller.calendar.updateFields();
2976
* @function Move forward one time increment
2978
timeNext: function () {
2979
var newDate = this.controller.date.addSeconds(this.timeIncrement);
2980
this.controller.setDate(newDate);
2981
this.controller.calendar.updateFields();
2985
* @fileOverview Contains the class definition for an UserSettings class.
2986
* Syntax: Prototype, jQuery
2987
* @author <a href="mailto:keith.hughitt@gmail.com">Keith Hughitt</a>
2989
/*global Class, CookieJar, $H */
2990
var UserSettings = Class.create(
2991
/** @lends UserSettings.prototype */
2995
* @description Creates a new UserSettings instance. Because all cookie data is stored as strings, numeric types
2996
* must be specified (e.g. in _INTGER_PARAMS) so that the application knows to parse them as such.
2997
* @param {Object} controller A reference to the Helioviewer application class
2999
initialize: function (controller) {
3000
this.controller = controller;
3003
* @description Default user settings
3005
this._DEFAULTS = $H({
3006
'obs-date' : 1065312000000,
3007
'zoom-level' : this.controller.defaultZoomLevel,
3008
'tile-layers' : [{ tileAPI: "api/index.php", observatory: 'SOH', instrument: 'EIT', detector: 'EIT', measurement: '195' }],
3009
'warn-zoom-level' : false,
3010
'warn-mouse-coords' : false,
3012
'VSOService::noaa': 'small-blue-circle',
3013
'GOESXRayService::GOESXRay': 'small-green-diamond',
3014
'VSOService::cmelist': 'small-yellow-square'
3018
this._INTEGER_PARAMS = $A(['obs-date', 'zoom-level']);
3019
this._FLOAT_PARAMS = $A([]);
3020
this._BOOLEAN_PARAMS = $A(['warn-zoom-level', 'warn-mouse-coords']);
3023
this.cookies = new CookieJar({
3024
expires: 31536000, //1 year
3028
if (!this._exists()) {
3029
this._loadDefaults();
3034
* @description Saves a specified setting
3035
* @param {String} key The setting to update
3036
* @param {JSON} value The new value for the setting
3038
set: function (key, value) {
3039
if (this._validate(key, value)) {
3040
this.cookies.put(key, value);
3042
//console.log("Ignoring invalid user-setting...");
3047
* @description Gets a specified setting
3048
* @param {String} key The setting to retrieve
3049
* @returns {JSON} The value of the desired setting
3051
get: function (key) {
3052
// Parse numeric types
3053
if (this._INTEGER_PARAMS.include(key)) {
3054
return parseInt(this.cookies.get(key));
3056
else if (this._FLOAT_PARAMS.include(key)) {
3057
return parseFloat(this.cookies.get(key));
3059
else if (this._BOOLEAN_PARAMS.include(key)) {
3060
return this.cookies.get(key) == "true" ? true : false;
3062
return this.cookies.get(key);
3066
* @description Checks to see if user-settings cookies have been set.
3068
_exists: function () {
3069
return (this.cookies.getKeys().length > 0);
3073
* @description Validates a setting (Currently checks observation date and zoom-level)
3074
* @param {String} setting The setting to be validated
3075
* @param {String} value The value of the setting to check
3077
_validate: function (setting, value) {
3085
if ((isNaN(value)) || (value < this.controller.minZoomLevel) || (value > this.controller.maxZoomLevel)) {
3096
* @description Resets a single setting to it's default value
3097
* @param {String} setting The setting for which the default value should be loaded
3099
_resetSetting: function (setting) {
3100
this.set(setting, this._getDefault(setting));
3104
* @description Gets the default value for a given setting
3105
* @param {Object} setting
3107
_getDefault: function (setting) {
3108
return this._DEFAULTS.get(setting);
3112
* @description Loads defaults if cookies have not been set prior.
3114
_loadDefaults: function () {
3116
this._DEFAULTS.each(function (setting) {
3117
self.set(setting.key, setting.value);
3122
* @fileOverview Contains the class definition for an Viewport class.
3123
* @author <a href="mailto:keith.hughitt@gmail.com">Keith Hughitt</a>
3124
* @author <a href="mailto:patrick.schmiedel@gmx.net">Patrick Schmiedel</a>
3125
* @see ViewportHandlers
3127
/*global Class, UIElement, $, Builder, Element, ViewportHandlers, document */
3128
var Viewport = Class.create(UIElement,
3129
/** @lends Viewport.prototype */
3132
* @description Default Viewport settings
3136
headerId: 'middle-col-header',
3141
prefetch: 0 //Pre-fetch any tiles that fall within this many pixels outside the physical viewport
3144
dimensions: { width: 0, height: 0 },
3148
* @description Creates a new Viewport
3149
* @param {Object} controller A Reference to the Helioviewer application class
3150
* @param {Object} options Custom Viewport settings
3152
* <br><div style='font-size:16px'>Options:</div><br>
3153
* <div style='margin-left:15px'>
3154
* <b>zoomLevel</b> - The default zoomlevel to display (should be passed in from Helioviewer).<br>
3155
* <b>headerId</b> - Helioviewer header section id.<br>
3156
* <b>footerId</b> - Helioviewer footer section id.<br>
3157
* <b>tileSize</b> - Size of tiles.<br>
3158
* <b>debug</b> - Display additional information for debugging purposes.<br>
3159
* <b>prefetch</b> - The radius outside of the visible viewport to prefetch.<br>
3162
initialize: function (controller, options) {
3163
Object.extend(this, this.defaultOptions);
3164
Object.extend(this, options);
3166
var center, centerBox;
3168
this.domNode = $(this.id);
3169
this.innerNode = $(this.id + '-container-inner');
3170
this.outerNode = $(this.id + '-container-outer');
3171
this.controller = controller;
3172
this.mouseCoords = "disabled";
3173
this.ViewportHandlers = new ViewportHandlers(this);
3175
// Combined height of the header and footer in pixels (used for resizing viewport vertically)
3176
this.headerAndFooterHeight = $(this.headerId).getDimensions().height + $(this.footerId).getDimensions().height + 4;
3178
// Resize to fit screen
3181
// Determine center of viewport
3182
center = this.getCenter();
3184
// Create a container to limit how far the layers can be moved
3185
this.sandbox = $(this.domNode.appendChild(Builder.node('div', {className: 'sandbox'})));
3186
this.sandbox.setStyle({'position': 'absolute', 'width': '0px', 'height': '0px', 'left': center.x + 'px', 'top': center.y + 'px'});
3188
// Create a master container to make it easy to manipulate all layers at once
3189
this.movingContainer = $(this.sandbox.appendChild(Builder.node('div', {className: 'movingContainer'})));
3190
this.movingContainer.setStyle({'left': 0, 'top': 0});
3192
// For Debugging purposes only
3194
this.movingContainer.setStyle({'border': '1px solid red'});
3196
centerBox = new Element('div', {id: 'vp-debug-center', style: 'position: absolute; width: 50px; height: 50px; border: 1px dashed red; '});
3197
centerBox.setStyle({'left': (center.x - 25) + 'px', 'top': (center.y - 25) + 'px'});
3198
this.domNode.insert(centerBox);
3203
* @description Centers the viewport.
3205
center: function () {
3206
//var center = this.getCenter();
3207
var sb = this.sandbox.getDimensions();
3209
this.moveTo(0.5 * sb.width, 0.5 * sb.height);
3213
* @description Move the viewport focus to a new location.
3214
* @param {Int} x X-value
3215
* @param {Int} y Y-value
3217
moveTo: function (x, y) {
3218
this.movingContainer.setStyle({
3224
this.fire('move', { x: x, y: y });
3228
* @description Moves the viewport's focus
3229
* @param {Int} x X-value
3230
* @param {Int} y Y-value
3232
moveBy: function (x, y) {
3233
// Sandbox dimensions
3234
var sandbox = this.sandbox.getDimensions(),
3237
x: Math.min(Math.max(this.startMovingPosition.x - x, 0), sandbox.width),
3238
y: Math.min(Math.max(this.startMovingPosition.y - y, 0), sandbox.height)
3241
this.movingContainer.setStyle({
3247
this.fire('move', { x: pos.x, y: pos.y });
3251
* @description Event-handler for a mouse-drag start.
3253
startMoving: function () {
3254
this.startMovingPosition = this.getContainerPos();
3258
* @description Get the coordinates of the viewport center
3259
* @returns {Object} The X & Y coordinates of the viewport's center
3261
getCenter: function () {
3263
x: Math.round(this.domNode.getWidth() / 2),
3264
y: Math.round(this.domNode.getHeight() / 2)
3269
* @description Get the current coordinates of the moving container
3270
* @returns {Object} The X & Y coordinates of the viewport's top-left corner
3272
getContainerPos: function () {
3274
x: parseInt(this.movingContainer.getStyle('left'), 10),
3275
y: parseInt(this.movingContainer.getStyle('top'), 10)
3280
* @description Alias for getContainerPos function
3281
* @returns {Object} The X & Y coordinates of the viewport's top-left corner
3283
currentPosition: function () {
3284
return this.getContainerPos();
3288
* @description Another alias for getContainerPos: returns the pixel coorindates of the HelioCenter relative to the viewport top-left corner.
3289
* @returns {Object} The X & Y coordinates of the viewport's top-left corner
3291
helioCenter: function () {
3292
return this.getContainerPos();
3296
* @description Event handler fired after dragging
3298
endMoving: function () {
3302
* @description Algorithm for determining which tiles should be displayed at
3303
* a given time. Uses the Heliocentric coordinates of the viewport's
3304
* TOP-LEFT and BOTTOM-RIGHT corners to determine range to display.
3306
checkTiles: function () {
3311
indices = this.displayRange();
3313
// Update visible array
3314
for (i = indices.xStart; i <= indices.xEnd; i += 1) {
3315
for (j = indices.yStart; j <= indices.yEnd; j += 1) {
3316
if (!this.visible[i]) {
3317
this.visible[i] = [];
3319
this.visible[i][j] = true;
3325
* @description Update the size and location of the movement-constraining box.
3327
updateSandbox: function () {
3328
var maxDimensions, old, center, newSize, change, movingContainerOldPos, newHCLeft, newHCTop, padHeight, shiftTop;
3330
this.dimensions = this.domNode.getDimensions();
3331
maxDimensions = this.controller.layerManager.getMaxDimensions();
3332
old = this.sandbox.getDimensions();
3333
center = this.getCenter();
3335
// New sandbox dimensions
3337
width : Math.max(0, maxDimensions.width - this.dimensions.width),
3338
height: Math.max(0, maxDimensions.height - this.dimensions.height)
3342
$('vp-debug-center').setStyle({'left': center.x - 25 + 'px', 'top': center.y - 25 + 'px'});
3347
x: newSize.width - old.width,
3348
y: newSize.height - old.height
3351
// Initial moving container position
3352
movingContainerOldPos = this.movingContainer.positionedOffset();
3354
// Update sandbox dimensions
3355
this.sandbox.setStyle({
3356
width : newSize.width + 'px',
3357
height : newSize.height + 'px',
3358
left : center.x - (0.5 * newSize.width) + 'px',
3359
top : center.y - (0.5 * newSize.height) + 'px'
3362
// Update moving container position
3363
newHCLeft = Math.max(0, Math.min(newSize.width, movingContainerOldPos[0] + (0.5 * change.x)));
3364
newHCTop = Math.max(0, Math.min(newSize.height, movingContainerOldPos[1] + (0.5 * change.y)));
3366
this.movingContainer.setStyle({
3367
left: newHCLeft + 'px',
3368
top : newHCTop + 'px'
3373
* @description Returns the range of indices for the tiles to be displayed.
3374
* @returns {Object} The range of tiles which should be displayed
3376
displayRange: function () {
3379
// Get heliocentric viewport coordinates
3380
vp = this.getHCViewportPixelCoords();
3382
// Expand to fit tile increment
3385
top: vp.top - ts - (vp.top % ts),
3386
left: vp.left - ts - (vp.left % ts),
3387
bottom: vp.bottom + ts - (vp.bottom % ts),
3388
right: vp.right + ts - (vp.right % ts)
3391
// Indices to display (one subtracted from ends to account for "0th" tiles).
3392
this.visibleRange = {
3393
xStart: vp.left / ts,
3394
xEnd: (vp.right / ts) - 1,
3395
yStart: vp.top / ts,
3396
yEnd: (vp.bottom / ts) - 1
3399
return this.visibleRange;
3403
* @description Returns the heliocentric coordinates of the upper-left and bottom-right corners of the viewport
3404
* @returns {Object} The coordinates for the top-left and bottom-right corners of the viewport
3406
getHCViewportPixelCoords: function () {
3407
var sb, mc, vpDimensions;
3409
sb = this.sandbox.positionedOffset();
3410
mc = this.movingContainer.positionedOffset();
3411
vpDimensions = this.domNode.getDimensions();
3414
left: -(sb[0] + mc[0]),
3415
top : -(sb[1] + mc[1]),
3416
right: vpDimensions.width - (sb[0] + mc[0]),
3417
bottom: vpDimensions.height - (sb[1] + mc[1])
3422
* @description Zooms To a specified zoom-level.
3423
* @param {Int} zoomLevel The desired zoomLevel
3425
zoomTo: function (zoomLevel) {
3426
this.zoomLevel = zoomLevel;
3430
this.controller.layerManager.resetLayers(this.visible);
3433
this.updateSandbox();
3436
this.controller.userSettings.set('zoom-level', zoomLevel);
3440
* @description Adjust viewport dimensions when window is resized.
3442
resize: function () {
3443
var oldDimensions, h, viewportOuter;
3446
oldDimensions = this.dimensions;
3448
// Ensure minimum height
3449
h = Math.max(this.minHeight, document.viewport.getHeight() - this.headerAndFooterHeight);
3451
//Update viewport height
3452
viewportOuter = this.outerNode;
3453
viewportOuter.setStyle({height: h + 'px'});
3455
this.dimensions = this.domNode.getDimensions();
3457
this.dimensions.width += this.prefetch;
3458
this.dimensions.height += this.prefetch;
3460
if (this.dimensions.width !== oldDimensions.width || this.dimensions.height !== oldDimensions.height) {
3461
if (this.controller.layerManager.layers.length > 0) {
3462
this.updateSandbox();
3464
this.controller.layerManager.resetLayers(this.visible);
3470
* @fileOverview Contains the class definition for an ViewportHandlers class.
3471
* @author <a href="mailto:keith.hughitt@gmail.com">Keith Hughitt</a>
3472
* @author <a href="mailto:patrick.schmiedel@gmx.net">Patrick Schmiedel</a>
3474
/*global Class, document, window, Event, $, $$ */
3475
var ViewportHandlers = Class.create(
3476
/** @lends ViewportHandlers.prototype */
3478
startingPosition: { x: 0, y: 0 },
3479
mouseStartingPosition: { x: 0, y: 0 },
3480
mouseCurrentPosition: { x: 0, y: 0 },
3481
mouseCoords : { x: 0, y: 0 },
3484
naturalZoomLevel : 10,
3485
naturalResolution : 2.63,
3486
rSunArcSeconds : 975,
3490
* @description Contains a collection of event-handlers for dealing with Viewport-related events
3492
* @param {Object} viewport A Reference to the Helioviewer application class
3494
initialize: function (viewport) {
3495
this.viewport = viewport;
3497
//Mouse-related event-handlers
3498
//Event.observe(window, 'mousemove', this.mouseMove.bindAsEventListener(this));
3499
Event.observe(document, 'mousemove', this.mouseMove.bindAsEventListener(this));
3500
//Event.observe(window, 'mouseup', this.mouseUp.bindAsEventListener(this));
3501
Event.observe(document, 'mouseup', this.mouseUp.bindAsEventListener(this));
3502
Event.observe(viewport.domNode, 'mousedown', this.mouseDown.bindAsEventListener(this));
3505
Event.observe(this.viewport.domNode, 'dblclick', this.doubleClick.bindAsEventListener(this));
3508
Event.observe(this.viewport.domNode, "mousewheel", this.mouseWheel.bindAsEventListener(this), false);
3509
Event.observe(this.viewport.domNode, "DOMMouseScroll", this.mouseWheel.bindAsEventListener(this), false); // Firefox
3511
//Keyboard-related event-handlers
3512
Event.observe(document, 'keypress', this.keyPress.bindAsEventListener(this));
3513
Event.observe(window, 'keypress', this.keyPress.bindAsEventListener(this));
3517
* @description Fired when a mouse is pressed
3518
* @param {Event} event Prototype Event class
3520
mouseDown: function (event) {
3521
//this.viewport.output('down');n
3522
this.viewport.isMoving = true;
3523
this.startingPosition = this.viewport.currentPosition();
3524
this.mouseStartingPosition = {
3525
x: Event.pointerX(event),
3526
y: Event.pointerY(event)
3528
this.viewport.domNode.setStyle({ cursor: 'all-scroll' });
3529
if (this.viewport.domNode.setCapture) {
3530
this.viewport.domNode.setCapture();
3532
this.viewport.startMoving();
3536
* @description Handles double-clicks
3537
* @param {Event} e Prototype Event class
3539
doubleClick: function (e) {
3541
viewport = this.viewport;
3543
//check to make sure that you are not already at the minimum/maximum zoom-level
3544
if ((e.shiftKey || (viewport.zoomLevel > viewport.controller.minZoomLevel)) && (viewport.zoomLevel < viewport.controller.maxZoomLevel)) {
3545
if (e.isLeftClick()) {
3547
pos = this.getRelativeCoords(e.pointerX(), e.pointerY());
3549
//var test = 'position: absolute; background-color: yellow; border: 1px solid blue; width:5px; height: 5px; left:' + pos.x + 'px; top: ' + pos.y + 'px;';
3550
//viewport.domNode.insert(new Element('div', {style: test}));
3553
this.viewport.startMoving();
3557
viewport.moveBy(0.5 * pos.x, 0.5 * pos.y);
3558
viewport.controller.zoomControl.zoomButtonClicked(1);
3561
viewport.moveBy(2 * pos.x, 2 * pos.y);
3562
viewport.controller.zoomControl.zoomButtonClicked(-1);
3566
//console.log("Out of bounds double-click request! See Viewport.js:57");
3571
* @description Handles mouse-wheel movements
3572
* @param {Event} event Prototype Event class
3574
mouseWheel: function (e) {
3575
this.viewport.controller.zoomControl.zoomButtonClicked(-Event.wheel(e));
3579
* @description Get the mouse-coords relative to top-left of the viewport frame
3580
* @param {Int} screenx X-dimensions of the user's screen
3581
* @param {Int} screeny Y-dimensions of the user's screen
3583
getRelativeCoords: function (screenx, screeny) {
3584
var vp, offset, mouseCoords;
3588
//Compute offset from top-left of browser viewport
3589
//var xOffset = $('left-col').getDimensions().width + Math.round(0.03 * vp.outerNode.getDimensions().width) + 2;
3590
//var yOffset = $(vp.headerId).getDimensions().height + Math.round(0.03 * vp.outerNode.getDimensions().height) + 3;
3591
offset = $('helioviewer-viewport-container-inner').positionedOffset();
3593
// Mouse-coordinates relative to the top-left of the viewport
3594
//var mouseCoords = {
3595
// x: screenx - xOffset,
3596
// y: screeny - yOffset
3599
x: screenx - offset[0] - 1,
3600
y: screeny - offset[1] - 1
3606
* @description Keyboard-related event-handlers
3608
keyPress: function (e) {
3609
var key = e.keyCode;
3611
//Ignore event if user is type in an input form field
3612
if (e.target.tagName !== "INPUT") {
3614
//Arrow keys (move viewport)
3615
if (key === 37 || key === 38 || key === 39 || key === 40) {
3616
this.startingPosition = this.viewport.currentPosition();
3617
this.viewport.startMoving();
3618
this.moveCounter = (this.moveCounter + 1) % this.moveThrottle;
3619
if (this.moveCounter !== 0) {
3625
this.viewport.moveBy(8, 0);
3629
else if (e.keyCode === 38) {
3630
this.viewport.moveBy(0, 8);
3633
else if (e.keyCode === 39) {
3634
this.viewport.moveBy(-8, 0);
3638
else if (e.keyCode === 40) {
3639
this.viewport.moveBy(0, -8);
3646
* @description Fired when a mouse button is released
3647
* @param {Event} event Prototype Event object
3649
mouseUp: function (event) {
3650
//this.viewport.output('up');
3651
this.viewport.isMoving = false;
3652
this.viewport.domNode.setStyle({ cursor: 'pointer' });
3653
if (this.viewport.domNode.releaseCapture) {
3654
this.viewport.domNode.releaseCapture();
3656
this.viewport.endMoving();
3660
* @description Fired when a keyboard key is released
3661
* @param {Object} event Prototype Event object
3663
keyRelease: function (event) {
3664
this.viewport.isMoving = false;
3665
this.viewport.endMoving();
3669
* @description Handle drag events
3670
* @param {Object} event Prototype Event object
3672
mouseMove: function (event) {
3673
if (!this.viewport.isMoving) {
3677
var sb = this.viewport.sandbox.getDimensions();
3678
if ((sb.width === 0) && (sb.height === 0)) {
3682
this.moveCounter = (this.moveCounter + 1) % this.moveThrottle;
3683
if (this.moveCounter !== 0) {
3687
this.mouseCurrentPosition = {
3688
x: Event.pointerX(event),
3689
y: Event.pointerY(event)
3692
this.viewport.moveBy(this.mouseStartingPosition.x - this.mouseCurrentPosition.x, this.mouseStartingPosition.y - this.mouseCurrentPosition.y);
3696
* @description Toggles mouse-coords visibility
3698
toggleMouseCoords: function () {
3699
var vp, mouseCoordsX, mouseCoordsY, updateMouseCoords, self = this;
3700
// Case 1: Disabled -> Arcseconds
3701
if (this.viewport.mouseCoords === "disabled") {
3702
this.viewport.mouseCoords = "arcseconds";
3703
$('mouse-coords').toggle();
3705
// Case 2: Arcseconds -> Polar Coords
3706
else if (this.viewport.mouseCoords === "arcseconds") {
3707
this.viewport.mouseCoords = "polar";
3709
// Case 3: Polar Coords -> Disabled
3711
$('mouse-coords').toggle();
3712
this.viewport.mouseCoords = "disabled";
3713
//console.log("Polar Coords -> Disabled");
3717
if (this.viewport.controller.userSettings.get('warn-mouse-coords') === "false") {
3718
this.viewport.controller.messageConsole.log("Note: Mouse-coordinates should not be used for science operations!");
3719
this.viewport.controller.userSettings.set('warn-mouse-coords', true);
3722
// Cartesian & Polar coords
3723
if (this.viewport.mouseCoords !== "disabled") {
3724
mouseCoordsX = $('mouse-coords-x');
3725
mouseCoordsY = $('mouse-coords-y');
3728
mouseCoordsX.update("");
3729
mouseCoordsY.update("");
3731
// Remove existing event handler if switching from cartesian -> polar
3732
if (this.viewport.mouseCoords === "polar") {
3733
Event.stopObserving(this.viewport.movingContainer, "mousemove");
3737
updateMouseCoords = function (e) {
3738
var VX, negSV, SV, SM, MX, scale, x, y, polar;
3740
// Store current mouse-coordinates
3741
self.mouseCoords = {x: e.pageX, y: e.pageY};
3744
self.moveCounter = (self.moveCounter + 1) % self.moveThrottle;
3745
if (self.moveCounter !== 0) {
3749
// Coordinates realtive to viewport top-left corner
3750
VX = self.getRelativeCoords(e.pageX, e.pageY);
3751
negSV = self.viewport.sandbox.positionedOffset();
3756
SM = $$('.movingContainer')[0].positionedOffset();
3758
x: VX.x + (SV.x - SM[0]),
3759
y: VX.y + (SV.y - SM[1])
3763
scale = self.naturalResolution * Math.pow(2, self.viewport.zoomLevel - self.naturalZoomLevel);
3764
x = Math.round((scale * MX.x));
3765
y = - Math.round((scale * MX.y));
3768
if (self.viewport.mouseCoords === "arcseconds") {
3769
mouseCoordsX.update("x: " + x + " ′′");
3770
mouseCoordsY.update("y: " + y + " ′′");
3774
polar = Math.toPolarCoords(x, -y);
3776
mouseCoordsX.update(((polar.r / self.rSunArcSeconds) + "").substring(0, 5) + " R<span style='vertical-align: sub; font-size:10px;'>☉</span>");
3777
mouseCoordsY.update(Math.round(polar.theta) + " °");
3780
Event.observe(this.viewport.movingContainer, "mousemove", updateMouseCoords);
3782
// Execute handler once immediately to show new coords
3783
updateMouseCoords({pageX: this.mouseCoords.x, pageY: this.mouseCoords.y});
3786
Event.stopObserving(this.viewport.movingContainer, "mousemove");
3791
* @fileOverview Contains the class definition for an ZoomControl class.
3792
* @author <a href="mailto:keith.hughitt@gmail.com">Keith Hughitt</a>
3793
* @author <a href="mailto:patrick.schmiedel@gmx.net">Patrick Schmiedel</a>
3794
* @see The <a href="http://helioviewer.org/mediawiki-1.11.1/index.php?title=Zoom_Levels_and_Observations">HelioViewer Wiki</a>
3795
* for more information on zoom levels.
3797
/*global UIElement, Class, Control, Event, $R, $ */
3798
var ZoomControl = Class.create(UIElement,
3799
/** @lends ZoomControl.prototype */
3803
* @description Creates a new ZoomControl
3804
* @param {Object} controller A Reference to the Helioviewer application class
3805
* @param {Object} options Custom ZoomControl settings
3807
initialize: function (controller, options) {
3808
Object.extend(this, options);
3809
this.controller = controller;
3810
this.domNode = $(this.id);
3811
this.handle = $(this.id + 'Handle');
3813
var range = $R(this.minZoomLevel, this.maxZoomLevel);
3814
this.slider = new Control.Slider(this.handle, this.id + 'Track', {
3817
sliderValue: this.zoomLevel,
3819
onChange: this.changed.bind(this)
3821
//this.handle.innerHTML = this.zoomLevel;
3822
Event.observe($(this.id + 'ZoomIn'), 'click', this.zoomButtonClicked.bind(this, -1));
3823
Event.observe($(this.id + 'ZoomIn'), 'mousedown', function (e) {
3826
Event.observe($(this.id + 'ZoomOut'), 'mouseup', this.zoomButtonClicked.bind(this, 1));
3827
Event.observe($(this.id + 'ZoomOut'), 'mousedown', function (e) {
3833
* @description Increases or decreases zoom level in response to pressing the plus/minus buttons.
3834
* @param {Integer} dir The amount to adjust the zoom level by (+1 or -1).
3836
zoomButtonClicked: function (dir) {
3837
this.slider.setValue(this.slider.value + dir);
3841
* @description Adjusts the zoom-control slider
3842
* @param {Integer} v The new zoom value.
3844
changed: function (v) {
3845
this.fire('change', v);
b'\\ No newline at end of file'