~jstys-z/helioviewer.org/client5

« back to all changes in this revision

Viewing changes to lib/helioviewer/build/helioviewer-all.js

  • Committer: V. Keith Hughitt
  • Date: 2009-03-26 19:20:57 UTC
  • Revision ID: hughitt1@kore-20090326192057-u0x8rf8sf5lmmnwh
nightly build 03-26-2009: Using alpha-channel JPEG 2000 dataset

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/**
 
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>
 
5
 */
 
6
 /*global Class, Ajax, $*/
 
7
var LoadingIndicator = Class.create(
 
8
        /** @lends LoadingIndicator.prototype */
 
9
        {
 
10
        /**
 
11
         * @constructs
 
12
         * @description Creates a new LoadingIndicator
 
13
         */
 
14
        initialize: function () {
 
15
                this.loadingItems = [];
 
16
                
 
17
                Ajax.Responders.register({
 
18
            onCreate:   this.loadingStarted.bind(this, 'Ajax'),
 
19
                    onComplete: this.loadingFinished.bind(this, 'Ajax')
 
20
                });
 
21
        },
 
22
 
 
23
        /**
 
24
         * @description Display the loading indicator
 
25
         */
 
26
        show: function () {
 
27
                //Effect.Appear($('loading'), { duration: 1 });
 
28
                $('loading').show();
 
29
        },
 
30
        
 
31
        /**
 
32
         * @description Hide the loading indicator
 
33
         */
 
34
        hide: function () {
 
35
                //Effect.Fade($('loading'), { duration: 1 });
 
36
                $('loading').hide();
 
37
        },
 
38
        
 
39
        /**
 
40
         * @description Reset the loading indicator
 
41
         */
 
42
        reset: function () {
 
43
                this.loadingItems.length = 0;
 
44
                this.hide();
 
45
        },
 
46
        
 
47
        /**
 
48
         * @description Add an AJAX request to the loading stack
 
49
         * @param {Object} e Event
 
50
         */
 
51
        loadingStarted: function (e) {
 
52
                this.show();
 
53
                this.loadingItems.push({});
 
54
        },
 
55
        
 
56
        /**
 
57
         * @description Remove an AJAX request from the loading stack
 
58
         * @param {Object} i Item to remove
 
59
         */
 
60
        loadingFinished: function (i) {
 
61
                this.loadingItems.pop();
 
62
                if (this.loadingItems.length === 0) {
 
63
                    this.hide();
 
64
                }
 
65
        }
 
66
});/**
 
67
 * @fileOverview Contains the class definition for an UIElement class.
 
68
 * @author <a href="mailto:patrick.schmiedel@gmx.net">Patrick Schmiedel</a>
 
69
 */
 
70
 /*global Class, $A */
 
71
var UIElement = Class.create(
 
72
        /** @lends UIElement.prototype */
 
73
        {
 
74
        /**
 
75
         * @description Enables inheriting classes to use the "event/notification" system
 
76
         * @constructs
 
77
         */
 
78
        initialize: function () {},
 
79
        
 
80
        /**
 
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
 
84
         */
 
85
        addObserver: function (eventName, callback) {
 
86
            if (!this.observers) {
 
87
                this.observers = [];
 
88
            }
 
89
            if (!this.observers[eventName]) {
 
90
                    this.observers[eventName] = $A([]);
 
91
            }
 
92
            this.observers[eventName].push(callback);
 
93
            return this;
 
94
        },
 
95
 
 
96
        /**
 
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
 
100
         */
 
101
        fire: function (eventName, eventParameters) {
 
102
                //$('output').innerHTML = eventName + ': ' + eventParameters;
 
103
            if (!this.observers || !this.observers[eventName] || this.observers[eventName].length === 0) {
 
104
                        return this;
 
105
            }
 
106
            this.observers[eventName].each(function (callback) {
 
107
                callback(eventParameters);
 
108
            });
 
109
            return this;
 
110
        }
 
111
});/**
 
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>
 
115
 */
 
116
/*global Layer, Class, UIElement, Element, $ */
 
117
var Layer = Class.create(UIElement, 
 
118
        /** @lends Layer.prototype */
 
119
        {
 
120
        maxZoomLevel: 20, // ZoomLevel where FullSize = 1px
 
121
        minZoomLevel: 10,
 
122
        visible: true,
 
123
 
 
124
        /**
 
125
         * @constructs
 
126
         * @description Creates a new Layer
 
127
         * @param {Object} viewport Viewport to place the layer in
 
128
         * <br>
 
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>
 
134
         * </div>
 
135
         */
 
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);
 
141
        },
 
142
 
 
143
        /**
 
144
         * @description Adjust the Layer's z-index 
 
145
         * @param {Object} val Z-index to use
 
146
         */
 
147
        setZIndex: function (val) {
 
148
                this.domNode.setStyle({ zIndex: val });
 
149
        },
 
150
 
 
151
        /**
 
152
         * @description Sets the Layer's visibility
 
153
         * @param {Boolean} visible Hide/Show layer 
 
154
         * @returns {Boolean} Returns new setting
 
155
         */
 
156
        setVisible: function (visible) {
 
157
                this.visible = visible;
 
158
                this.domNode.setStyle({ visibility: (visible ? 'visible' : 'hidden') });
 
159
                return this.visible;
 
160
        },
 
161
 
 
162
        /**
 
163
         * @description Toggle layer visibility
 
164
         */
 
165
        toggleVisible: function () {
 
166
                return this.setVisible(!this.visible);
 
167
        }
 
168
});/**
 
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
 
172
 * 
 
173
 * Syntax: jQuery, Prototype
 
174
 */
 
175
/*global Class, Calendar, $, UIElement, jQuery, window */
 
176
var Calendar = Class.create(UIElement,
 
177
        /** @lends Calendar.prototype */
 
178
        {
 
179
        /**
 
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.
 
184
         * @constructs 
 
185
         */ 
 
186
    initialize: function (controller, dateFieldId, timeFieldId) {
 
187
        this.controller = controller;
 
188
        this.dateField = $(dateFieldId);
 
189
        this.timeField = $(timeFieldId);
 
190
        
 
191
        var self = this;
 
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',
 
197
            mandatory: true,
 
198
            showOn: 'both',
 
199
            yearRange:  '1998:2008',
 
200
            onSelect: function (dateStr) {
 
201
                window.setTimeout(function () {
 
202
                    var time = self.timeField.value,
 
203
                        date = Date.parse(dateStr),
 
204
                        
 
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),
 
209
                    
 
210
                        //Convert to UTC
 
211
                        utcDate = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate(), hours, minutes, seconds));
 
212
                    
 
213
                    self.fire('observationDateChange', utcDate);
 
214
                }, 500);
 
215
                
 
216
            }
 
217
        });
 
218
    },
 
219
    
 
220
        /**
 
221
         * @description Updates the HTML form fields associated with the calendar.
 
222
         */
 
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();
 
227
    }
 
228
});
 
229
/**
 
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
 
233
 *
 
234
 * Syntax: jQuery, Prototype
 
235
 */
 
236
/*global Class, Layer, EventLayer, $, jQuery, $A, Ajax, Element, EventMarker, navigator */
 
237
var EventLayer = Class.create(Layer,
 
238
        /** @lends EventLayer.prototype */
 
239
        {
 
240
        /**
 
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>
 
245
         */
 
246
        defaultOptions: {
 
247
                type: 'EventLayer',
 
248
                sunRadius0: 94 * (1 << 12),
 
249
                opacityGroupId: -1,
 
250
                displayLabels: false,
 
251
                windowSize: 43200,
 
252
                eventMarkerOffset: {
 
253
                        top: 10,
 
254
                        left: 0
 
255
                }
 
256
        },
 
257
 
 
258
        /**
 
259
         * @constructs
 
260
         * @description Creates a new EventLayer.
 
261
         * @param {Object} viewport A reference to Helioviewer's viewport.
 
262
         * @param {Object} options The event layer settings.
 
263
         */
 
264
        initialize: function (viewport, options) {
 
265
                Object.extend(this, this.defaultOptions);
 
266
                Object.extend(this, options);
 
267
                this.viewport = viewport;
 
268
 
 
269
                this.id = 'eventlayer' + new Date().getTime();
 
270
 
 
271
                this.events = $A([]);
 
272
 
 
273
                this.domNode = new Element('div', {className: 'event-layer'});
 
274
                this.viewport.movingContainer.appendChild(this.domNode);
 
275
        
 
276
                // Add to eventLayer Accordion (if is not already there).
 
277
                this.eventAccordion.addLayer(this);
 
278
                
 
279
                this.queryEvents();
 
280
        },
 
281
 
 
282
        /**
 
283
         * @description Sends an AJAX request to get a list of events.
 
284
         */
 
285
        queryEvents: function () {
 
286
                // Ajax responder
 
287
                var processResponse = function (transport) {
 
288
                        this.clear();
 
289
                        if (transport.responseJSON) {
 
290
                                this.displayEvents(transport.responseJSON);
 
291
                        }                       
 
292
                },
 
293
                        xhr, queryDate = null;
 
294
                
 
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();
 
299
                }
 
300
                else {
 
301
                        queryDate = this.viewport.controller.date.toISOString().slice(1, -1);
 
302
                }
 
303
 
 
304
                // Ajax request
 
305
                xhr = new Ajax.Request(this.viewport.controller.eventAPI, {
 
306
                        method: 'POST',
 
307
                        onSuccess: processResponse.bind(this),
 
308
                        parameters: {
 
309
                                action: 'getEvents',
 
310
                                date: queryDate,
 
311
                                windowSize: this.windowSize,
 
312
                                catalogs: this.catalog
 
313
                        }
 
314
                });
 
315
        },
 
316
 
 
317
        /**
 
318
         * @description Place-holder for an event-handler which will handle viewportMove events.
 
319
         */
 
320
        viewportMove: function () {
 
321
 
 
322
        },
 
323
        
 
324
        /**
 
325
         * @description Removes all events from the screen and clears event container
 
326
         */
 
327
        clear: function () {
 
328
                this.events.each(function (e) {
 
329
                        e.remove();
 
330
                });
 
331
                this.events.clear();
 
332
        },
 
333
 
 
334
        /**
 
335
         * @description Draws events to screen
 
336
         * @param {JSON} jsonEvents An JSON array of event meta-information
 
337
         */
 
338
        displayEvents: function (jsonEvents) {
 
339
                var self = this,
 
340
                        date = this.viewport.controller.date,
 
341
                        UTCOffset = - parseInt(date.getTimezoneOffset(), 10) * 60,
 
342
                        sunRadius = this.sunRadius0 >> this.viewport.zoomLevel;
 
343
                
 
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}));
 
347
                });
 
348
        },
 
349
        
 
350
        /**
 
351
         * @description Updates the icon associated with the EventLayer.
 
352
         * @param {String} newIcon New icon to use.
 
353
         */
 
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)';
 
358
                
 
359
                // Update event markers
 
360
                this.events.each(function (event) {
 
361
                        event.marker.setStyle({
 
362
                                'background': url
 
363
                        });
 
364
                });
 
365
                
 
366
                // Update event accordion icon
 
367
                jQuery('#event-icon-' + this.id).css('background', url);
 
368
                
 
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);
 
372
        },
 
373
 
 
374
        /**
 
375
         * @description Toggle event label visibility
 
376
         */
 
377
        toggleLabelVisibility: function () {
 
378
                this.displayLabels = !this.displayLabels;
 
379
                this.events.each(function (e) {
 
380
                e.toggleLabel();
 
381
                });
 
382
        },
 
383
 
 
384
        /**
 
385
         * @description Reload event-layer
 
386
         */
 
387
        reload: function () {
 
388
                this.queryEvents();
 
389
        },
 
390
 
 
391
        /**
 
392
         * @description Reset event-layer
 
393
         */
 
394
        reset: function () {
 
395
                var sunRadius = this.sunRadius0 >> this.viewport.zoomLevel;
 
396
 
 
397
                this.events.each(function (e) {
 
398
                        e.refresh(sunRadius);
 
399
                });
 
400
        }
 
401
});
 
402
/**
 
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
 
407
 * 
 
408
 * Syntax: jQuery, Prototype
 
409
 */
 
410
/*global Class, EventLayerAccordion, $, jQuery, Ajax, Event, EventLayer, Hash, IconPicker, Layer */
 
411
var EventLayerAccordion = Class.create(Layer,
 
412
        /** @lends EventLayerAccordion.prototype */
 
413
        {
 
414
        /**
 
415
         * @constructs
 
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.
 
419
         */
 
420
        initialize: function (viewport, containerId) {
 
421
                this.viewport = viewport;
 
422
                this.container = jQuery('#' + containerId);
 
423
                
 
424
                // Default icons
 
425
                this.eventIcons = this.viewport.controller.userSettings.get('event-icons');
 
426
 
 
427
                // Setup menu UI components
 
428
                this._setupUI();
 
429
                
 
430
                // Setup icon-picker
 
431
                this.iconPicker = new IconPicker('event-icon-menu');
 
432
 
 
433
                // Initialize accordion
 
434
                this.domNode = jQuery('#EventLayerAccordion-Container');
 
435
                this.domNode.dynaccordion();
 
436
 
 
437
                // Get Event Catalogs
 
438
                this.getEventCatalogs();
 
439
        },
 
440
 
 
441
        /**
 
442
         * @description Adds a new entry to the event layer accordion
 
443
         * @param {Object} layer EventLayer to add to the accordion.
 
444
         */
 
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;
 
450
                
 
451
                layer.icon = this.eventIcons[catalog.id] || 'small-blue-circle';
 
452
                
 
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>";
 
457
 
 
458
                // Create accordion entry body
 
459
                body = '<div style="color: white; position: relative;">' + catalog.description + '</div>';
 
460
 
 
461
                //Add to accordion
 
462
                this.domNode.dynaccordion("addSection", {id: layer.id, header: head, cell: body});
 
463
 
 
464
                // Event-handlers
 
465
                this._setupEventHandlers(layer);
 
466
        },
 
467
 
 
468
        /**
 
469
         * @description Get a list of the available event catalogs.
 
470
         */
 
471
        getEventCatalogs: function () {
 
472
                //@TODO: Move this to the new layer manager.
 
473
                
 
474
                // Ajax responder
 
475
                var processResponse = function (transport) {
 
476
                        var lm = this.viewport.controller.layerManager,
 
477
                                catalogs = transport.responseJSON,
 
478
                                self = this
 
479
                        
 
480
                        if (typeof(catalogs) !== "undefined") {
 
481
                                this.eventCatalogs = new Hash();
 
482
                                
 
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);
 
487
                                        }
 
488
                                });
 
489
                                
 
490
                                //Initial catalogs to load
 
491
                                lm.addLayer(new EventLayer(this.viewport, {
 
492
                                        catalog: "VSOService::cmelist",
 
493
                                        eventAccordion: this
 
494
                                }));
 
495
                                lm.addLayer(new EventLayer(this.viewport, {
 
496
                                        catalog: "GOESXRayService::GOESXRay",
 
497
                                        eventAccordion: this
 
498
                                }));
 
499
                                lm.addLayer(new EventLayer(this.viewport, {
 
500
                                        catalog: "VSOService::noaa",
 
501
                                        eventAccordion: this,
 
502
                                        windowSize: 86400
 
503
                                }));
 
504
                        } else {
 
505
                                this._catalogsUnavailable();
 
506
                        }
 
507
                },
 
508
 
 
509
                xhr = new Ajax.Request(this.viewport.controller.eventAPI, {
 
510
                        method: "POST",
 
511
                        parameters: { action: "getEventCatalogs" }, 
 
512
                        onSuccess: processResponse.bind(this)
 
513
                });
 
514
        },
 
515
 
 
516
        /**
 
517
         * @description Setup empty event layer accordion.
 
518
         */
 
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'}),
 
523
                        self = this;
 
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>'));
 
526
                
 
527
                // Event-handlers
 
528
                addLayerBtn.click(function () {
 
529
                        if (typeof(self.eventCatalogs) !== "undefined") {
 
530
                                //Populate select-box
 
531
                                var select = self._buildCatalogSelect(),
 
532
                                
 
533
                                        okayBtn = "<button>Ok</button>",
 
534
                                
 
535
                                        //Add to accordion
 
536
                                        tmpId = 'tmp' + new Date().getTime();
 
537
                                self.domNode.dynaccordion("addSection", {
 
538
                                        id: tmpId,
 
539
                                        header: '<div class="layer-Head">Select catalog to use:</div>',
 
540
                                        cell: select + okayBtn,
 
541
                                        open: true
 
542
                                });
 
543
                                
 
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', {
 
548
                                                id: tmpId
 
549
                                        });
 
550
                                        self.viewport.controller.layerManager.addLayer(new EventLayer(self.viewport, {
 
551
                                                catalog: select.value,
 
552
                                                eventAccordion: self
 
553
                                        }));
 
554
                                });
 
555
                        } else {
 
556
                                //Do nothing
 
557
                        }
 
558
                });
 
559
        },
 
560
        
 
561
        /**
 
562
         * @description Builds a SELECT menu with all the catalogs not already displayed.
 
563
         */
 
564
        _buildCatalogSelect: function () {
 
565
                var self = this,
 
566
                        select = "<select class='event-layer-select' style='margin:5px;'>";
 
567
 
 
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>";
 
571
                        }
 
572
                });
 
573
                select += "</select>";
 
574
                
 
575
                return select;
 
576
        },
 
577
        
 
578
        /**
 
579
         * @description Display error message to let user know service is down.
 
580
         */
 
581
        _catalogsUnavailable: function () {
 
582
                // Handle button
 
583
                $('eventAccordion').select('a.gray')[0].update("");
 
584
                
 
585
                // Error message
 
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});
 
589
        },
 
590
 
 
591
        /**
 
592
         * @description Sets up UI-related event handlers
 
593
         * @param {Object} layer EventLayer being added to the accordion.
 
594
         */
 
595
        _setupEventHandlers: function (layer) {
 
596
                var visibilityBtn = jQuery("#visibilityBtn-" + layer.id),
 
597
                        removeBtn = jQuery("#removeBtn-" + layer.id),
 
598
                        eventIcon = jQuery("#event-icon-" + layer.id),
 
599
                        self = this,
 
600
 
 
601
                /**
 
602
                 * @inner
 
603
                 * @description Toggles layer visibility
 
604
                 * @param {Object} e jQuery Event Object.
 
605
                 */
 
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 + ')');
 
610
                        e.stopPropagation();
 
611
                },
 
612
 
 
613
                /**
 
614
                 * @inner
 
615
                 * @description Layer remove button event-handler
 
616
                 * @param {Object} e jQuery Event Object.
 
617
                 */
 
618
                removeLayer = function (e) {
 
619
                        var self = e.data;
 
620
                        self.viewport.controller.layerManager.removeLayer(layer);
 
621
                        self.domNode.dynaccordion('removeSection', {id: layer.id});
 
622
                        e.stopPropagation();
 
623
                },
 
624
                
 
625
                /**
 
626
                 * @inner
 
627
                 * @description Icon selection menu event-handler
 
628
                 * @param {Object} e jQuery Event Object.
 
629
                 */
 
630
                showIconMenu = function (e) {
 
631
                        layer = e.data;
 
632
                        self.iconPicker.toggle(layer, jQuery(this).position());
 
633
                        e.stopPropagation();
 
634
                };
 
635
 
 
636
                visibilityBtn.bind('click', this, toggleVisibility);
 
637
                removeBtn.bind('click', this, removeLayer);
 
638
                eventIcon.bind('click', layer, showIconMenu);
 
639
        }
 
640
});
 
641
 
 
642
/**
 
643
 * @author <a href="mailto:keith.hughitt@gmail.com">Keith Hughitt</a>
 
644
 * @fileoverview Contains the class definition for an EventMarker class.
 
645
 * @see EventLayer
 
646
 * 
 
647
 * Syntax: Prototype
 
648
 */
 
649
/*global EventMarker, Class, $, $$, $H, Element, Event, Tip */
 
650
var EventMarker = Class.create(
 
651
        /** @lends EventMarker.prototype */
 
652
        {
 
653
        /**
 
654
         * @constructs
 
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
 
662
         */    
 
663
    initialize: function (eventLayer, event, date, utcOffset, sunRadius, options) {
 
664
        Object.extend(this, event);
 
665
                Object.extend(this, options);
 
666
                
 
667
                this.eventLayer = eventLayer;
 
668
                this.appDate = date;
 
669
                this.utcOffset = utcOffset;
 
670
                this.sunRadius = sunRadius;
 
671
                
 
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;
 
676
                
 
677
                //Create container
 
678
                this.pos = {
 
679
                        x: this.sunX * sunRadius,
 
680
                        y: this.sunY * sunRadius
 
681
                };
 
682
                
 
683
                this.container = this.eventLayer.domNode.appendChild(
 
684
                        new Element('div', {className: 'event', style: 'left: ' + (this.pos.x) + 'px; top: ' + (this.pos.y) + 'px;'})
 
685
                );
 
686
                                
 
687
                //Create dom-nodes for event marker, details label, and details popup
 
688
                this.createMarker();
 
689
                this.createLabel();
 
690
                this.createPopup();
 
691
        },
 
692
        
 
693
        /**
 
694
         * @description Creates the marker and adds it to the viewport
 
695
         */
 
696
        createMarker: function () {
 
697
                //make event-type CSS-friendly
 
698
                var cssType = this.type.gsub(' ', "_"),
 
699
                
 
700
                //create html dom-node
 
701
                marker = new Element('div', {className: 'event-marker'});
 
702
                
 
703
                marker.setStyle({
 
704
                        'background': 'url(images/events/' + this.eventLayer.icon + "-" + cssType + '.png)'
 
705
                });
 
706
                
 
707
                //var self = this;
 
708
                //marker.observe('click', function(event) {
 
709
                //      self.togglePopup();
 
710
                //});
 
711
                
 
712
                this.marker = marker;
 
713
                
 
714
                this.container.appendChild(marker);
 
715
        },
 
716
        
 
717
        /**
 
718
         * @description Creates a small block of text which is displayed when the user pressed the "d" key ("details").
 
719
         */
 
720
        createLabel: function () {
 
721
                var display = (this.eventLayer.displayLabels ? "inline" : "none"),
 
722
                        labelText = this.getLabelText(this.type),
 
723
                
 
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(),
 
727
                
 
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);
 
730
                
 
731
                //Adjust style to reflect time difference
 
732
        if (timeDiff < 0) {
 
733
                label.addClassName("timeBehind");
 
734
        }
 
735
        else if (timeDiff > 0) {
 
736
                label.addClassName("timeAhead");
 
737
        }
 
738
        
 
739
        this.label = label;
 
740
        
 
741
        this.container.appendChild(label);
 
742
                
 
743
        },
 
744
        
 
745
        /**
 
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
 
748
         */
 
749
        getLabelText: function (eventType) {
 
750
                var labelText = null;
 
751
                
 
752
                switch (eventType) {
 
753
 
 
754
                case "Active Region":
 
755
                        labelText = this.eventId;
 
756
                        break;
 
757
                case "CME":
 
758
                        labelText = this.time.startTime;
 
759
                        break;
 
760
                case "Type II Radio Burst":
 
761
                        labelText = this.time.startTime;
 
762
                        break;
 
763
                default:
 
764
                        labelText = this.time.startTime;
 
765
                        break;
 
766
                }
 
767
                
 
768
                return labelText;
 
769
        },
 
770
        
 
771
        /*
 
772
        createPopup: function () {
 
773
                var properties = $H(this.properties);
 
774
 
 
775
                var size = (properties.size() > 2 ? 'larger' : 'large');
 
776
 
 
777
                //popup
 
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 + "&deg; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;" +
 
785
                                        "<strong>Width:</strong> " + this.polarWidth + "&deg;<br>";
 
786
                properties.keys().each(function(key){
 
787
                        content += "<strong>" + key + ":</strong> " + properties.get(key) + "<br>";
 
788
                });             
 
789
                content += "<strong>Source:</strong> <a href='" + this.sourceUrl + "' class='event-url' target='_blank'>" + this.sourceUrl + "</a><br></div>";
 
790
                
 
791
                popup.update(content);
 
792
                
 
793
                //close button
 
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);
 
797
                
 
798
                this.popup = popup;
 
799
                this.container.appendChild(popup);                      
 
800
        },*/
 
801
        
 
802
        /**
 
803
         * @description Creates a popup which is displayed when the event marker is clicked
 
804
         */
 
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>";
 
812
                                        
 
813
                                        //"<strong>Position Angle:</strong> " + this.polarCpa + "&deg; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;" +
 
814
                                        //"<strong>Width:</strong> " + this.polarWidth + "&deg;<br>";
 
815
                                        
 
816
                properties.keys().each(function (key) {
 
817
                        content += "<strong>" + key + ":</strong> " + properties.get(key);
 
818
                        if (key.search(/angle/i) !== -1) {
 
819
                                content += "&deg;";
 
820
                        } 
 
821
                        content += "<br>";
 
822
                });
 
823
                content += "<strong>Source:</strong> <a href='" + this.sourceUrl + "' class='event-url' target='_blank'>" + this.sourceUrl + "</a><br></div>";
 
824
                
 
825
                t = new Tip(this.container, content, {
 
826
                        title: 'Details:',
 
827
                        style: 'protogrey',
 
828
                        stem: 'topLeft',
 
829
                        closeButton: true,
 
830
                        showOn: 'click',
 
831
                        hideOn: 'click',
 
832
                        hook: { target: 'bottomRight', tip: 'topLeft' },
 
833
                        offset: { x: 14, y: 14 }
 
834
                });
 
835
 
 
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'});
 
844
                                });
 
845
                        }
 
846
                });
 
847
 
 
848
                /*
 
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'});
 
854
                        });
 
855
                });*/
 
856
                
 
857
                /*      
 
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());
 
862
                        }
 
863
                        e.target.select('.prototip').first().setStyle({
 
864
                                        'top': '15px',
 
865
                                        'left': '15px'
 
866
                        });
 
867
                });*/
 
868
        },
 
869
        
 
870
        /**
 
871
         * @description Removes the EventMarker
 
872
         */
 
873
        remove: function () {
 
874
                this.container.remove();
 
875
        },
 
876
 
 
877
         /**
 
878
          * @description Redraws event
 
879
          * @param {Int} sunRadius The updated solar radius, in pixels.
 
880
          */
 
881
        refresh: function (sunRadius) {
 
882
                this.sunRadius = sunRadius;
 
883
                this.pos = {
 
884
                        x: this.sunX * sunRadius,
 
885
                        y: this.sunY * sunRadius
 
886
                };
 
887
                this.container.setStyle({
 
888
                        left: (this.pos.x - 2) + 'px',
 
889
                        top:  (this.pos.y - 2) + 'px'
 
890
                });
 
891
        },
 
892
 
 
893
        /**
 
894
         * @description Toggles event label visibility
 
895
         */     
 
896
        toggleLabel: function () {
 
897
                this.label.toggle();
 
898
        },
 
899
        
 
900
        /**
 
901
         * @description Toggle event popup visibility
 
902
         */     
 
903
        togglePopup: function () {
 
904
                this.popup.toggle();
 
905
        }
 
906
});
 
907
/**
 
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/
 
911
 * 
 
912
 * Syntax: Prototype
 
913
 */
 
914
/*global Class, $, EventTimeline, Timeline, UIElement, window */
 
915
var EventTimeline = Class.create(UIElement,
 
916
        /** @lends EventTimeline.prototype */
 
917
        {
 
918
        /**
 
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.
 
922
         * @constructs 
 
923
         */ 
 
924
    initialize: function (controller, container) {
 
925
                this.controller = controller;
 
926
                this.container  = container;
 
927
                this.resizeTimerID = null;
 
928
                
 
929
                this.eventSource = new Timeline.DefaultEventSource();
 
930
                var bandInfos = [
 
931
                        Timeline.createBandInfo({
 
932
                                eventSource:    this.eventSource,
 
933
                                width:          "70%", 
 
934
                                intervalUnit:   Timeline.DateTime.MONTH, 
 
935
                                intervalPixels: 100
 
936
                        }),
 
937
                        Timeline.createBandInfo({
 
938
                                eventSource:    this.eventSource,
 
939
                                width:          "30%", 
 
940
                                intervalUnit:   Timeline.DateTime.YEAR, 
 
941
                                intervalPixels: 200
 
942
                        })
 
943
                ],
 
944
                self = this;
 
945
                
 
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);
 
951
                });
 
952
        },
 
953
 
 
954
        /**
 
955
         * @description Event-hanlder for window resize
 
956
         */
 
957
        resize: function () {
 
958
                if (this.resizeTimerID === null) {
 
959
                        this.resizeTimerID = window.setTimeout(function () {
 
960
                                this.resizeTimerID = null;
 
961
                                this.timeline.layout();
 
962
                        }, 500);
 
963
                }
 
964
        }       
 
965
});/**
 
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>
 
969
 */
 
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 */
 
973
        {
 
974
        /**
 
975
         * @description Default Helioviewer options<br><br>
 
976
         */
 
977
        defaultOptions: {
 
978
                defaultZoomLevel: 12,
 
979
                baseZoom : 10,
 
980
                baseScale: 2.63,
 
981
                defaultPrefetchSize: 0,
 
982
                timeIncrementSecs: 86400,
 
983
                minZoomLevel: 8,
 
984
                maxZoomLevel: 16,
 
985
                tileAPI:  'api/index.php',
 
986
                imageAPI: 'api/index.php',
 
987
                eventAPI: 'api/index.php'
 
988
        },
 
989
 
 
990
        /**
 
991
         * @constructs
 
992
         * @description Creates a new Helioviewer instance.
 
993
         * @param {Object} options Custom application settings.
 
994
         * <br>
 
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>
 
1005
         * </div>
 
1006
         * @see Helioviewer#defaultOptions for a list of the available parameters.
 
1007
         */
 
1008
        initialize: function (options) {
 
1009
                Object.extend(this, this.defaultOptions);
 
1010
                Object.extend(this, options);
 
1011
 
 
1012
                // Loading indication
 
1013
                this.loadingIndicator = new LoadingIndicator();
 
1014
 
 
1015
                // Load user-settings
 
1016
                this.loadUserSettings();
 
1017
 
 
1018
                // Starting date
 
1019
                this.date = new Date(this.userSettings.get('obs-date'));
 
1020
                $('date').writeAttribute('value', this.date.toYmdUTCString());
 
1021
                $('time').writeAttribute('value', this.date.toHmUTCString());
 
1022
 
 
1023
                this.layerManager =  new LayerManager(this);
 
1024
                this.initViewports();
 
1025
                this.initUI();
 
1026
                this.initEvents();
 
1027
                this.initKeyBoardListeners();
 
1028
 
 
1029
                // Add initial layers
 
1030
                this.userSettings.get('tile-layers').each((function (settings) {
 
1031
                        this.layerManager.addLayer(new TileLayer(this.viewports[0], settings));
 
1032
                }).bind(this));
 
1033
 
 
1034
                //Shadow-box
 
1035
                //Shadowbox.init({skipSetup: true});
 
1036
 
 
1037
        },
 
1038
 
 
1039
        /**
 
1040
         * @description Initialize Helioviewer's user interface (UI) components
 
1041
         */
 
1042
        initUI: function () {
 
1043
                var centerBtn, mouseCoords;
 
1044
 
 
1045
                //Calendar
 
1046
                this.calendar = new Calendar(this, 'date', 'time');
 
1047
 
 
1048
                //Zoom-control
 
1049
                this.zoomControl = new ZoomControl(this, {
 
1050
                        id: 'zoomControl',
 
1051
                        zoomLevel:    this.userSettings.get('zoom-level'),
 
1052
                        minZoomLevel: this.minZoomLevel,
 
1053
                        maxZoomLevel: this.maxZoomLevel
 
1054
                });
 
1055
 
 
1056
                //Time-navigation controls
 
1057
                this.timeControls = new TimeControls(this, 'timestep-select', 'timeBackBtn', 'timeForwardBtn', this.timeIncrementSecs);
 
1058
 
 
1059
                //Message console
 
1060
                this.messageConsole = new MessageConsole(this, 'message-console', 'helioviewer-viewport-container-outer');
 
1061
 
 
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');
 
1065
 
 
1066
                //Tooltips
 
1067
                this.initToolTips();
 
1068
 
 
1069
                //Center button
 
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);
 
1073
 
 
1074
                //Mouse coordinates
 
1075
                mouseCoords =  Builder.node('div', {id: 'mouse-coords', style: 'display: none'});
 
1076
 
 
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);
 
1079
 
 
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);
 
1083
 
 
1084
                //About button
 
1085
                jQuery('#about-dialog').dialog({
 
1086
                        autoOpen: false,
 
1087
                        title: "Helioviewer - About",
 
1088
                        width: 480,
 
1089
                        height: 300,
 
1090
                        draggable: true
 
1091
                });
 
1092
                jQuery('#helioviewer-about').click(function (e) {
 
1093
                        if (jQuery('#about-dialog').dialog('isOpen')) {
 
1094
                                jQuery('#about-dialog').dialog('close');
 
1095
                        }
 
1096
                        else {
 
1097
                                jQuery('#about-dialog').dialog('open');
 
1098
                        }
 
1099
                });
 
1100
 
 
1101
                //Keyboard shortcuts dialog
 
1102
                jQuery('#keyboard-shortcuts-dialog').dialog({
 
1103
                        autoOpen: false,
 
1104
                        title:    "Helioviewer - Usage Tips",
 
1105
                        width: 480,
 
1106
                        height: 430,
 
1107
                        draggable: true
 
1108
                });
 
1109
                jQuery('#helioviewer-shortcuts').click(function (e) {
 
1110
                        if (jQuery('#keyboard-shortcuts-dialog').dialog('isOpen')) {
 
1111
                                jQuery('#keyboard-shortcuts-dialog').dialog('close');
 
1112
                        }
 
1113
                        else {
 
1114
                                jQuery('#keyboard-shortcuts-dialog').dialog('open');
 
1115
                        }
 
1116
                });
 
1117
 
 
1118
                //Movie builder
 
1119
                //this.movieBuilder = new MovieBuilder({id: 'movieBuilder', controller: this});
 
1120
 
 
1121
                // Timeline
 
1122
                //this.timeline = new EventTimeline(this, "timeline");
 
1123
        },
 
1124
 
 
1125
        /**
 
1126
         * @description Loads user settings from cookies or defaults if no settings have been stored.
 
1127
         */
 
1128
        loadUserSettings: function () {
 
1129
                this.userSettings = new UserSettings(this);
 
1130
 
 
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);
 
1134
                }
 
1135
 
 
1136
                if (this.load["img-scale"]) {
 
1137
                        this.userSettings.set('zoom-level', this.scaleToZoomLevel(parseInt(this.load["img-scale"], 10)));
 
1138
                }
 
1139
 
 
1140
                if (this.load.layers) {
 
1141
                        var 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) });
 
1144
                        });
 
1145
 
 
1146
                        this.userSettings.set('tile-layers', layers);
 
1147
                }
 
1148
 
 
1149
        },
 
1150
 
 
1151
        /**
 
1152
         * @description Initialize Helioviewer's viewport(s).
 
1153
         */
 
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 })
 
1157
                ]);
 
1158
 
 
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));
 
1162
                });
 
1163
        },
 
1164
 
 
1165
        /**
 
1166
         * @description Initialize event-handlers for UI components controlled by the Helioviewer class
 
1167
         */
 
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));
 
1173
        },
 
1174
 
 
1175
        /**
 
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>
 
1178
         */
 
1179
        initKeyBoardListeners: function () {
 
1180
                var self = this;
 
1181
                Event.observe(document, 'keypress', function (e) {
 
1182
 
 
1183
                        //Ignore event if user is type in an input form field
 
1184
                        if (e.target.tagName !== "INPUT") {
 
1185
                                var code, character;
 
1186
                                if (!e) {
 
1187
                                        e = window.event;
 
1188
                                }
 
1189
                                if (e.keyCode) {
 
1190
                                        code = e.keyCode;
 
1191
                                }
 
1192
                                else if (e.which) {
 
1193
                                        code = e.which;
 
1194
                                }
 
1195
 
 
1196
                                character = String.fromCharCode(code);
 
1197
 
 
1198
                                //TODO: use events or public method instead of zoomControl's (private) method.
 
1199
                                if (character === "-" || character === "_") {
 
1200
                                        self.zoomControl.zoomButtonClicked(+1);
 
1201
                                }
 
1202
                                else if (character === "=" || character === "+") {
 
1203
                                        self.zoomControl.zoomButtonClicked(-1);
 
1204
                                }
 
1205
                                else if (character === "c") {
 
1206
                                        self.viewports.each(function (viewport) {
 
1207
                                                viewport.center();
 
1208
                                        });
 
1209
                                }
 
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();
 
1215
                                                }
 
1216
                                        });
 
1217
                                }
 
1218
                                // toggle mouse-coords display
 
1219
                                else if (character === "m") {
 
1220
                                        self.viewports.each(function (viewport) {
 
1221
                                                viewport.ViewportHandlers.toggleMouseCoords();
 
1222
                                        });
 
1223
                                }
 
1224
 
 
1225
                        }
 
1226
                });
 
1227
        },
 
1228
 
 
1229
        /**
 
1230
         * @description Adds tooltips to all elements that are loaded everytime (buttons, etc) using default tooltip options.
 
1231
         */
 
1232
        initToolTips: function () {
 
1233
                var items = $A([
 
1234
                        '#zoomControlZoomIn',
 
1235
                        '#zoomControlZoomOut',
 
1236
                        '#zoomControlHandle',
 
1237
                        '#timeBackBtn',
 
1238
                        '#timeForwardBtn'
 
1239
                ]),
 
1240
 
 
1241
                self = this;
 
1242
                items.each(function (item) {
 
1243
                        self.addToolTip(item, {yOffset: -125});
 
1244
                });
 
1245
 
 
1246
                //Handle some special cases separately
 
1247
                this.addToolTip('#movieBuilder', {position: 'topleft'});
 
1248
 
 
1249
        },
 
1250
 
 
1251
        /**
 
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.
 
1255
         */
 
1256
        addToolTip: function (id, params) {
 
1257
                var options = params || [],
 
1258
                        classname = "tooltip-" + (options.position || "bottomleft") + "-" + (options.tooltipSize || "medium");
 
1259
 
 
1260
                jQuery(id).tooltip({
 
1261
                        delay: (options.delay ? options.delay : 1000),
 
1262
                        track: (options.track ? options.track : false),
 
1263
                        showURL: false,
 
1264
                        opacity: 1,
 
1265
                        fixPNG: true,
 
1266
                        showBody: " - ",
 
1267
                        extraClass: classname,
 
1268
                        top: (options.yOffset ? options.yOffset : 0),
 
1269
                        left: 12
 
1270
                });
 
1271
        },
 
1272
 
 
1273
        /**
 
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.
 
1276
         */
 
1277
        addViewport: function (viewport) {
 
1278
                this.viewports.push(viewport);
 
1279
                return this;
 
1280
        },
 
1281
 
 
1282
        /**
 
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
 
1287
         * @see UIElement
 
1288
         */
 
1289
        observe: function (uielement, eventName, eventHandler) {
 
1290
                uielement.addObserver(eventName, eventHandler.bind(this));
 
1291
        },
 
1292
 
 
1293
        /**
 
1294
         * @description Sets the desired viewing date and time.
 
1295
         * @param {Date} date A JavaScript Date object with the new time to use
 
1296
         */
 
1297
        setDate: function (date) {
 
1298
                this.date = 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();
 
1302
        },
 
1303
 
 
1304
        /**
 
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
 
1307
         */
 
1308
        scaleToZoomLevel: function (imgScale) {
 
1309
                var zoomOffset = Math.round(Math.lg((imgScale / this.baseScale)));
 
1310
                return this.baseZoom + zoomOffset;
 
1311
        },
 
1312
 
 
1313
        /**
 
1314
         * @namespace
 
1315
         * @description Helioviewer application-level event handlers
 
1316
         */
 
1317
        handlers: {
 
1318
                /**
 
1319
                 * @description Changes the zoom-level to a new value
 
1320
                 * @param {Int} level The new zoom-level to use
 
1321
                 */
 
1322
                zoom: function (level) {
 
1323
                        this.viewports.each(function (viewport) {
 
1324
                                viewport.zoomTo(level);
 
1325
                        });
 
1326
                },
 
1327
 
 
1328
                observationDateChange: function (date) {
 
1329
                        this.setDate(date);
 
1330
                        this.calendar.updateFields();
 
1331
                },
 
1332
 
 
1333
                observationTimeChange: function (e) {
 
1334
                        var time = e.target.value,
 
1335
                                regex, newTime, hours, mins, secs;
 
1336
 
 
1337
                        //make sure time entered in correct format
 
1338
                        regex = /^\d{2}:\d{2}:\d{2}?/;
 
1339
 
 
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();
 
1347
 
 
1348
                                this.date.addHours(hours);
 
1349
                                this.date.addMinutes(mins);
 
1350
                                this.date.setSeconds(secs);
 
1351
 
 
1352
                                this.setDate(this.date);
 
1353
 
 
1354
                        } else {
 
1355
                                this.messageConsole.warn('Invalid time. Please enter a time in of form HH:MM:SS');
 
1356
                        }
 
1357
                },
 
1358
 
 
1359
                newToolTip: function (tooltip) {
 
1360
                        this.addToolTip(tooltip.id, tooltip.params);
 
1361
                },
 
1362
 
 
1363
                /**
 
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
 
1367
                 */
 
1368
                newLayer: function (data) {
 
1369
                        var inst = data.instrument,
 
1370
                                ui =   data.menuEntry,
 
1371
                                viewport = this.viewports[0],
 
1372
                                layer;
 
1373
 
 
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);
 
1377
 
 
1378
                        //Update menu entry display
 
1379
                        ui.layer = layer;
 
1380
                        ui.displayTileLayerOptions();
 
1381
                }
 
1382
        }
 
1383
});
 
1384
 
 
1385
/**
 
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>
 
1389
 */
 
1390
/*global Event, Element, navigator */
 
1391
/**
 
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.
 
1396
 */
 
1397
String.prototype.padLeft = function (padding, minLength) {
 
1398
        var str = this,
 
1399
                pad = '' + padding;
 
1400
        while (str.length < minLength) {
 
1401
                str = pad + str;
 
1402
        }
 
1403
        return str;
 
1404
};
 
1405
/**
 
1406
 * @description Trims a string from the left.
 
1407
 * @param {String} padding Character to trim.
 
1408
 * @returns {String} The resulting string.
 
1409
 */
 
1410
String.prototype.trimLeft = function (padding) {
 
1411
        var str = this,
 
1412
                pad = '' + padding;
 
1413
        while (str[0] === pad) {
 
1414
            str = str.substr(1);
 
1415
        }
 
1416
        return str;
 
1417
};
 
1418
 
 
1419
/**
 
1420
 * @description Outputs a UTC Date string of the format "YYYY/MM/dd"
 
1421
 * @returns {String} Datestring.
 
1422
 */
 
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);
 
1428
};
 
1429
 
 
1430
/**
 
1431
 * @description Outputs a UTC Date string of the format "HH:mm:ss"
 
1432
 * @returns {String} Datestring.
 
1433
 */
 
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);
 
1439
};
 
1440
 
 
1441
/**
 
1442
 * @description Takes a localized javascript date and returns a date set to the UTC time.
 
1443
 */
 
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),
 
1449
        
 
1450
                numSecs = (3600 * hours) + (60 * mins);
 
1451
        
 
1452
        if (sign === "+") {
 
1453
                numSecs = - numSecs;
 
1454
        }
 
1455
        
 
1456
        this.addSeconds(numSecs);
 
1457
};
 
1458
 
 
1459
Element.addMethods({
 
1460
        /**
 
1461
         * @name Event.observeOnce
 
1462
         * @description Prototype observeOnce function, Courtesy of Kangax
 
1463
         */
 
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);
 
1468
                });
 
1469
        })
 
1470
});
 
1471
 
 
1472
/**
 
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
 
1475
 */
 
1476
var getOS = function () {
 
1477
        var os = "other";
 
1478
        
 
1479
        if (navigator.appVersion.indexOf("Win") !== -1) {
 
1480
                os = "win";
 
1481
        }
 
1482
        if (navigator.appVersion.indexOf("Mac") !== -1) {
 
1483
                os = "mac";
 
1484
        }
 
1485
        if (navigator.appVersion.indexOf("X11") !== -1) {
 
1486
                os = "linux";
 
1487
        }
 
1488
        if (navigator.appVersion.indexOf("Linux") !== -1) {
 
1489
                os = "linux";
 
1490
        }
 
1491
        
 
1492
        return os;
 
1493
};
 
1494
 
 
1495
/**
 
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 
 
1500
 */
 
1501
Math.toPolarCoords = function (x, y) {
 
1502
        var radians = Math.atan(y / x);
 
1503
        
 
1504
        if  ((x > 0) && (y < 0)) {
 
1505
                radians += (2 * Math.PI);
 
1506
        }
 
1507
        else if (x < 0) {
 
1508
                radians += Math.PI;
 
1509
        }
 
1510
        else if ((x === 0) && (y > 0)) {
 
1511
                radians = Math.PI / 2;
 
1512
        }
 
1513
        else if ((x === 0) && (y < 0)) {
 
1514
                radians = (3 * Math.PI) / 2;
 
1515
        }
 
1516
                
 
1517
        return {
 
1518
                r     : Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2)),
 
1519
                theta : (180 / Math.PI) * radians
 
1520
        };
 
1521
};
 
1522
 
 
1523
/**
 
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
 
1527
 */
 
1528
Math.lg = function (x) {
 
1529
        return (Math.log(x) / Math.log(2));
 
1530
};
 
1531
/**
 
1532
 * @fileOverview Contains the IconPicker class definition.
 
1533
 * Syntax: Prototype, jQuery 
 
1534
 * @author <a href="mailto:keith.hughitt@gmail.com">Keith Hughitt</a>
 
1535
 */
 
1536
/*global IconPicker, Class, $, $A, jQuery*/
 
1537
var IconPicker = Class.create(
 
1538
        /** @lends IconPicker.prototype */
 
1539
        {
 
1540
        /**
 
1541
         * @constructs
 
1542
         * @description Creates a new IconPicker
 
1543
         * @param {String} id The identifier to use for the icon-picker dom-node
 
1544
         */
 
1545
        initialize: function (id) {
 
1546
                this.id = id;
 
1547
                
 
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"]);
 
1551
                
 
1552
                // Keep track of which event-layer is being targeted.
 
1553
                this.focus = null;
 
1554
                
 
1555
                // Build icon-list
 
1556
                this._buildIconList();
 
1557
        },
 
1558
        
 
1559
        
 
1560
        /**
 
1561
         * @description Sets up the list of available icons to chose from
 
1562
         */
 
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">'));
 
1567
 
 
1568
                i = 1;
 
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();
 
1575
                                });
 
1576
                                menu.append(icon);
 
1577
                                if (i % 3 === 0) {
 
1578
                                        menu.append(jQuery("<br>"));
 
1579
                                }
 
1580
                                i += 1;
 
1581
                        });                     
 
1582
                });
 
1583
                
 
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();
 
1586
                });
 
1587
                menu.append(closeBtn);
 
1588
                menu.append("</div>");
 
1589
                
 
1590
                jQuery('body').append(menu);
 
1591
        },
 
1592
        
 
1593
        /**
 
1594
         * @description Toggle IconPicker visibility
 
1595
         * @param {Object} layer The EventLayer icon picker is associated with.
 
1596
         * @param {Object} pos The mouse-click position
 
1597
         */
 
1598
        toggle: function (layer, pos) {
 
1599
                this.focus = layer;
 
1600
                jQuery('#' + this.id).css({'left': pos.left + 16, 'top': pos.top + 16}).slideToggle();
 
1601
        }
 
1602
});
 
1603
/**
 
1604
 * @fileOverview Contains class definition for a simple layer manager
 
1605
 * @author <a href="mailto:keith.hughitt@gmail.com">Keith Hughitt</a>
 
1606
 */
 
1607
/*global LayerManager, Class, UIElement, $A */
 
1608
var LayerManager = Class.create(UIElement,
 
1609
        /** @lends LayerManager.prototype */
 
1610
        {
 
1611
    /**
 
1612
     * @constructs
 
1613
     * @description Creates a new LayerManager
 
1614
     * @param {Object} A Rseference to the main application controller
 
1615
     */
 
1616
    initialize: function (controller) {
 
1617
        this.controller = controller;
 
1618
        this.layers = $A([]);
 
1619
    },
 
1620
    
 
1621
    //hasId: function (id) {
 
1622
                //return (this.layers.grep(id).length > 0 ? true : false);
 
1623
        //},
 
1624
 
 
1625
        /**
 
1626
         * @description Add a new layer
 
1627
         */
 
1628
        addLayer: function (layer) {
 
1629
                this.layers.push(layer);
 
1630
        },
 
1631
        
 
1632
        /**
 
1633
         * @description Adds a layer that is not already displayed
 
1634
         */
 
1635
        addNewLayer: function () {
 
1636
                var priorityQueue, currentLayers, p, defaultChoice = "SOHEITEIT171";
 
1637
                priorityQueue = $A([
 
1638
                        "SOHEITEIT195", "SOHLAS0C20WL", "SOHLAS0C30WL", "SOHLAS0C20WL", "SOHMDIMDImag", "SOHMDIMDIint", "SOHEITEIT171", "SOHEITEIT284", "SOHEITEIT304"
 
1639
                ]);
 
1640
                
 
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);
 
1645
                });
 
1646
                
 
1647
                // remove existing layers from queue
 
1648
                currentLayers.each(function(id) {
 
1649
                        priorityQueue = priorityQueue.without(id);
 
1650
                });
 
1651
                
 
1652
                p = priorityQueue.first() || defaultLayer;
 
1653
                
 
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();
 
1656
        },
 
1657
        
 
1658
        /**
 
1659
         * @description Gets the number of TileLayers currently loaded
 
1660
         * @return {Integer} Number of tile layers present.
 
1661
         */
 
1662
        numTileLayers: function () {
 
1663
                var n = 0;
 
1664
                this.layers.each(function (l) {
 
1665
                        if (l.type === "TileLayer") {
 
1666
                                n += 1;
 
1667
                        }
 
1668
                });
 
1669
                
 
1670
                return n;
 
1671
        },
 
1672
        
 
1673
        /**
 
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
 
1676
         */
 
1677
        getMaxDimensions: function () {
 
1678
                var maxWidth  = 0,
 
1679
                        maxHeight = 0;
 
1680
                
 
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); 
 
1687
                                }
 
1688
                        }
 
1689
                });
 
1690
                
 
1691
                //console.log("Max dimensions: " + maxWidth + ", " + maxHeight);
 
1692
                
 
1693
                return {width: maxWidth, height: maxHeight};
 
1694
        },
 
1695
        
 
1696
        /**
 
1697
         * @description Checks for presence of a specific event catalog
 
1698
         * @param {String} catalog Catalog ID
 
1699
         */
 
1700
        hasEventCatalog: function (catalog) {
 
1701
                return (this.eventLayers().find(function (l) {
 
1702
                        return l.catalog === catalog;
 
1703
                }) ? true : false);
 
1704
        },
 
1705
        
 
1706
        /**
 
1707
         * @description Returns only event-layers.
 
1708
         * @returns {Array} An array containing each of the currently displayed EVENT layers
 
1709
         */
 
1710
        eventLayers: function () {
 
1711
                return this.layers.findAll(function (l) { 
 
1712
                        return l.type === "EventLayer";
 
1713
                });
 
1714
        },
 
1715
        
 
1716
        /**
 
1717
         * @description Returns only tile-layers.
 
1718
         * @returns {Array} An array containing each of the currently displayed TILE layers
 
1719
         */
 
1720
        tileLayers: function () {
 
1721
                return this.layers.findAll(function (l) {
 
1722
                        return l.type === "TileLayer";
 
1723
                });
 
1724
        },
 
1725
        
 
1726
        /**
 
1727
         * @description Gets the number of event layers currently loaded
 
1728
         * @return {Integer} Number of event layers present.
 
1729
         */
 
1730
        numEventLayers: function () {
 
1731
                var n = 0;
 
1732
                this.layers.each(function (l) {
 
1733
                        if (l.type === "EventLayer") {
 
1734
                                n += 1;
 
1735
                        }
 
1736
                });
 
1737
                                
 
1738
                return n;
 
1739
        },
 
1740
        
 
1741
        /**
 
1742
         * @description Removes a layer
 
1743
         * @param {Object} The layer to remove
 
1744
         */
 
1745
        removeLayer: function (layer) {
 
1746
                layer.domNode.remove();
 
1747
                this.layers = this.layers.without(layer);
 
1748
        },
 
1749
        
 
1750
        /**
 
1751
         * @description Reload layers
 
1752
         */
 
1753
        reloadLayers: function () {
 
1754
                this.layers.each(function (layer) {
 
1755
                        layer.reload();
 
1756
                });
 
1757
        },
 
1758
 
 
1759
        /**
 
1760
         * @description Reloads each of the tile layers
 
1761
         */
 
1762
        resetLayers: function (visible) {
 
1763
                this.layers.each(function (layer) {
 
1764
                        layer.reset(visible);
 
1765
                });
 
1766
        },
 
1767
        
 
1768
        /**
 
1769
         * @description Updates the list of loaded tile layers stored in cookies
 
1770
         */
 
1771
        refreshSavedTileLayers: function () {
 
1772
                //console.log("refreshSavedTileLayers");
 
1773
                var tilelayers = [];
 
1774
                
 
1775
                this.tileLayers().each(function (layer) {
 
1776
                        var settings = {
 
1777
                                tileAPI     : layer.tileAPI,
 
1778
                                observatory : layer.observatory,
 
1779
                                instrument  : layer.instrument, 
 
1780
                                detector    : layer.detector,
 
1781
                                measurement : layer.measurement
 
1782
                        };
 
1783
                        
 
1784
                        tilelayers.push(settings);
 
1785
                });
 
1786
                
 
1787
                this.controller.userSettings.set('tile-layers', tilelayers);
 
1788
        }
 
1789
});
 
1790
/**
 
1791
 * @fileOverview Contains the "MessageConsole" class definition.
 
1792
 * @author <a href="mailto:keith.hughitt@gmail.com">Keith Hughitt</a>
 
1793
 */
 
1794
 /*global document, UIElement, Effect, $, Class, Element, Event, window */
 
1795
var MessageConsole = Class.create(UIElement ,
 
1796
        /** @lends MessageConsole.prototype */
 
1797
        {
 
1798
    /**
 
1799
     * @constructs
 
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
 
1809
     */
 
1810
    initialize: function (controller, container, viewport) {
 
1811
        this.controller = controller;
 
1812
        this.console =  $(container);
 
1813
        this.viewportId = viewport;
 
1814
    },
 
1815
    
 
1816
        /**
 
1817
         * @description Logs a message to the message-console
 
1818
         * @param {String} msg Message to display
 
1819
         */
 
1820
    log: function (msg) {
 
1821
        this.console.update(new Element('p', {style: 'color: #6495ED; font-weight: bold;'}).insert(msg));
 
1822
                var self = this,
 
1823
                trash = new Effect.Appear(this.console, { duration: 3.0 });
 
1824
    
 
1825
        //Hide the message after several seconds have passed
 
1826
        window.setTimeout(function () {
 
1827
            var trash = new Effect.Fade(self.console, { duration: 3.0 });
 
1828
        }, 6500);
 
1829
    },
 
1830
    
 
1831
    //info: function (msg) {
 
1832
    //},
 
1833
    
 
1834
        /**
 
1835
         * @description Displays a warning message in the message console
 
1836
         * @param {String} msg Message to display
 
1837
         */
 
1838
    warn: function (msg) {
 
1839
        this.console.update(new Element('p', {style: 'color: yellow; font-weight: bolder;'}).insert(msg));
 
1840
                var self = this,
 
1841
                trash = new Effect.Appear(this.console, { duration: 3.0 });
 
1842
    
 
1843
        //Hide the message after several seconds have passed
 
1844
        window.setTimeout(function () {
 
1845
            var trash = new Effect.Fade(self.console, { duration: 3.0 });
 
1846
        }, 6500);        
 
1847
    },
 
1848
    
 
1849
        /**
 
1850
         * @description Displays an error message in the message console
 
1851
         * @param {String} msg Message to display
 
1852
         */
 
1853
    error: function (msg) {
 
1854
        this.console.update(new Element('p', {style: 'color: red'}).insert(msg));
 
1855
        var self = this,
 
1856
                trash = new Effect.Shake(this.viewportId, {distance: 15, duration: 0.1});
 
1857
        trash = new Effect.Appear(this.console, { duration: 3.0 });
 
1858
    
 
1859
        //Hide the message after several seconds have passed
 
1860
        window.setTimeout(function () {
 
1861
                        var trash = new Effect.Fade(self.console, { duration: 3.0 });
 
1862
        }, 6500);
 
1863
    },
 
1864
    
 
1865
        /**
 
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 "#")
 
1869
         */
 
1870
    link: function (msg, linkText) {
 
1871
                var self = this,
 
1872
                        linkId, wrapper, link, trash;
 
1873
                        
 
1874
        // Generate a temporary id
 
1875
        linkId = 'link-' + this.controller.date.getTime() / 1000;
 
1876
        
 
1877
        // Html
 
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);
 
1882
        
 
1883
        this.console.update(new Element('p', {style: 'color: #6495ED;'}).insert(wrapper));
 
1884
        trash = new Effect.Appear(this.console, { duration: 2.0 });
 
1885
    
 
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();            
 
1890
        });
 
1891
        
 
1892
        return linkId;
 
1893
    }
 
1894
});/**
 
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>
 
1897
 *
 
1898
 */
 
1899
/*global MoviesBuilder, Class, UIElement, Event, document, window, Shadowbox, getOS, Ajax */
 
1900
//TODO: pass in bit-rate depending upon codec chosen! Xvid?
 
1901
 
 
1902
var MovieBuilder = Class.create(UIElement, 
 
1903
        /** @lends MovieBuilder.prototype */
 
1904
        {
 
1905
        /**
 
1906
         * @description Default MovieBuilder options
 
1907
         */
 
1908
        defaultOptions: {
 
1909
                active      : false,
 
1910
                url         : "api/index.php",
 
1911
                minZoomLevel: 13, //can relax for single layer movies...
 
1912
                numFrames   : 40,
 
1913
                frameRate   : 8,
 
1914
                sharpen     : false,
 
1915
                edgeEnhance : false,
 
1916
                format      : {win: "asf", mac: "mov", linux: "mp4"}
 
1917
        },
 
1918
 
 
1919
        /**
 
1920
     * @constructs
 
1921
     * @description Creates a new MovieBuilder
 
1922
     * @param {Object} options Custom MovieBuilder options
 
1923
     */
 
1924
    initialize: function (options) {
 
1925
                Object.extend(this, this.defaultOptions);
 
1926
                Object.extend(this, options);
 
1927
 
 
1928
        var self = this;
 
1929
 
 
1930
        //Quick Movie Event Handler
 
1931
                Event.observe(this.id, 'click', function () {
 
1932
                        if (!self.active) {
 
1933
                                var hv = self.controller,
 
1934
                                        hqFormat, displayRange, xhr;
 
1935
        
 
1936
                                self.active = true;
 
1937
                                
 
1938
                                // Chose an optimal codec based on User's OS
 
1939
                                hqFormat = self.format[getOS()];
 
1940
                                
 
1941
                                // Get range of tiles to use
 
1942
                                displayRange = hv.viewports[0].displayRange();
 
1943
        
 
1944
                                //Ajax Request
 
1945
                                xhr = new Ajax.Request(self.url, {
 
1946
                                        method: 'POST',
 
1947
                                        parameters: {
 
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,
 
1956
                                format:    hqFormat,
 
1957
                                xRange:    displayRange.xStart + ", " + displayRange.xEnd,
 
1958
                                                yRange:    displayRange.yStart + ", " + displayRange.yEnd
 
1959
                                        },
 
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.');
 
1963
                                                
 
1964
                                                self.active = false;
 
1965
        
 
1966
                                                Event.observe(linkId, 'click', function () {
 
1967
                                                        Shadowbox.open({
 
1968
                                                        player:  'iframe',
 
1969
                                                        title:   'Helioviewer Movie Player',
 
1970
                                                        height:   650,
 
1971
                                                        width:    550,
 
1972
                                                        content: self.url + '?action=playMovie&format=' + hqFormat + '&url=' + transport.responseJSON
 
1973
                                                        });
 
1974
                                                });
 
1975
                                        }
 
1976
                                });
 
1977
                        }
 
1978
                });
 
1979
    }
 
1980
});
 
1981
/**
 
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
 
1986
 * @requires Layer
 
1987
 * Syntax: jQuery, Prototype
 
1988
 */
 
1989
/*global TileLayer, Class, Layer, Ajax, Event, $, Element, Image */
 
1990
var TileLayer = Class.create(Layer, 
 
1991
        /** @lends TileLayer.prototype */
 
1992
        {       
 
1993
        /**
 
1994
         * @description Default TileLayer options
 
1995
         */
 
1996
        defaultOptions: {
 
1997
                type:             'TileLayer',
 
1998
                rootDir:          'tiles/',
 
1999
                cacheEnabled: true,
 
2000
                opacity:          100,
 
2001
                autoOpacity:  true,
 
2002
                startOpened:  false,
 
2003
                sharpen:      false
 
2004
        },
 
2005
 
 
2006
        /**
 
2007
         * @constructs
 
2008
         * @description Creates a new TileLayer
 
2009
         * @param {Object} viewport Viewport to place the tiles in
 
2010
         * <br>
 
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>
 
2020
         * </div>
 
2021
         */
 
2022
        initialize: function (viewport, options) {
 
2023
                Object.extend(this, this.defaultOptions);
 
2024
                Object.extend(this, options);
 
2025
                this.viewport = viewport;
 
2026
                
 
2027
                this.tileSize = viewport.tileSize; 
 
2028
                
 
2029
                this.layerManager = viewport.controller.layerManager;
 
2030
                this.id = 'tilelayer' + new Date().getTime();
 
2031
 
 
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);
 
2035
 
 
2036
                this.viewport.addObserver('move', this.viewportMove.bind(this));
 
2037
 
 
2038
                this.tiles = [];
 
2039
                this.loadClosestImage();
 
2040
        },
 
2041
 
 
2042
        /**
 
2043
         * @description Refreshes the TileLayer
 
2044
         */
 
2045
        reload: function () {
 
2046
                this.loadClosestImage();
 
2047
        },
 
2048
 
 
2049
        /**
 
2050
         * @function Remove TileLayer tiles
 
2051
         */
 
2052
        removeTiles: function () {
 
2053
                this.tiles = [];
 
2054
        },
 
2055
 
 
2056
        /**
 
2057
         * @description Reload the tile layer
 
2058
         * @param {Object} A 2-d binary array indicating which tiles have been (should be) loaded 
 
2059
         */
 
2060
        reset: function (visible) {
 
2061
                var i, j, currentScale, scaleOffset, old, numTiles, numTilesLoaded, indices, tile, onLoadComplete, self = this;
 
2062
                
 
2063
                // Start loading indicator
 
2064
                this.viewport.controller.loadingIndicator.loadingStarted();
 
2065
                
 
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;
 
2070
                
 
2071
                this.relWidth  = this.width  * scaleOffset;
 
2072
                this.relHeight = this.height * scaleOffset;
 
2073
                
 
2074
                //console.log("relative Width & Height: " + this.relWidth + " , " + this.relHeight);
 
2075
 
 
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);
 
2080
                }
 
2081
 
 
2082
                // Remove tiles in cache
 
2083
                this.removeTiles();
 
2084
 
 
2085
                this.refreshUTCDate();
 
2086
 
 
2087
                // Reference old tile nodes to remove after new ones are done loading
 
2088
                old = [];
 
2089
                this.domNode.childElements().each(function (tile) {
 
2090
                        old.push(tile);
 
2091
                });
 
2092
 
 
2093
                //TODO: Determine range to check
 
2094
                numTiles = 0;
 
2095
                numTilesLoaded = 0;
 
2096
 
 
2097
                indices = this.viewport.visibleRange;
 
2098
                
 
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) {
 
2106
                                                tile.remove();
 
2107
                                        }
 
2108
                                });
 
2109
                                self.viewport.controller.loadingIndicator.loadingFinished();
 
2110
                        }
 
2111
                };
 
2112
                
 
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)));
 
2117
 
 
2118
                                        if (!this.tiles[i]) {
 
2119
                                                this.tiles[i] = [];
 
2120
                                        }
 
2121
 
 
2122
                                        this.tiles[i][j] = {};
 
2123
                                        this.tiles[i][j].img = tile;
 
2124
 
 
2125
                                        numTiles += 1;
 
2126
 
 
2127
                                   // Makes sure all of the images have finished downloading before swapping them in
 
2128
                                        Event.observe(this.tiles[i][j].img, 'load', onLoadComplete);
 
2129
                                }
 
2130
                        }
 
2131
                }
 
2132
        },
 
2133
 
 
2134
        /**
 
2135
         * @description Update TileLayer date
 
2136
         */
 
2137
        refreshUTCDate: function () {
 
2138
                var date = new Date(this.timestamp * 1000);
 
2139
                date.toUTCDate();
 
2140
                this.utcDate = date;
 
2141
        },
 
2142
 
 
2143
        /**
 
2144
         * @description Store retrieved image properties
 
2145
         * @param {Object} imageProperties Properties of the image associated with the TileLayer  
 
2146
         */
 
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);
 
2151
                        return;
 
2152
                }
 
2153
                
 
2154
                Object.extend(this, imageProperties);
 
2155
 
 
2156
                this.fire('obs_time_change', this);
 
2157
 
 
2158
                //IE7: Want z-indices < 1 to ensure event icon visibility
 
2159
                this.setZIndex(parseInt(this.opacityGroupId, 10) - 10);
 
2160
 
 
2161
                //handle opacities for any overlapping images
 
2162
                if (this.autoOpacity) {
 
2163
                        this.setInitialOpacity();
 
2164
                        this.autoOpacity = false;
 
2165
                }
 
2166
 
 
2167
                // Let others know layer has been added
 
2168
                this.fire('change', this);
 
2169
 
 
2170
                this.viewport.checkTiles(true);
 
2171
 
 
2172
                this.reset(this.viewport.visible);
 
2173
        },
 
2174
 
 
2175
        /**
 
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 
 
2178
         */
 
2179
        setImage: function (imageId) {
 
2180
                if (imageId === this.imageId) {
 
2181
                        return;
 
2182
                }
 
2183
                this.imageId = imageId;
 
2184
                this.loadImageProperties();
 
2185
                this.reset(this.viewport.visible);
 
2186
        },
 
2187
 
 
2188
        /**
 
2189
         * @description Sets the opacity for the layer, taking into account layers which overlap one another.
 
2190
         */
 
2191
        setInitialOpacity: function () {
 
2192
                var self = this,
 
2193
                        opacity = 1,
 
2194
                        counter = 0;
 
2195
 
 
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)) {
 
2199
                                counter += 1;
 
2200
                        }
 
2201
                });
 
2202
                
 
2203
                //Do no need to adjust opacity if there is only one image
 
2204
                if (counter > 1) {
 
2205
                        opacity = opacity / counter;
 
2206
                        this.domNode.setOpacity(opacity);
 
2207
                        this.opacity = opacity * 100;
 
2208
                }
 
2209
 
 
2210
                /**
 
2211
                this.layerManager.layers.each (function (layer) {
 
2212
                        if (parseInt(layer.opacityGroupId) == parseInt(self.opacityGroupId)) {
 
2213
                           counter++;
 
2214
 
 
2215
                                //Do no need to adjust opacity of the first image
 
2216
                                if (counter > 1) {
 
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});
 
2221
                                }
 
2222
                        }
 
2223
                });*/
 
2224
        },
 
2225
 
 
2226
        /**
 
2227
         * @description Update the tile layer's opacity
 
2228
         * @param {int} Percent opacity to use
 
2229
         */
 
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) + ')'});
 
2235
        },
 
2236
 
 
2237
        /**
 
2238
         * @description Loads the closest image in time to that requested
 
2239
         */
 
2240
        loadClosestImage: function () {
 
2241
                var date = this.viewport.controller.date,
 
2242
                        processResponse, xhr;
 
2243
 
 
2244
                // Ajax responder
 
2245
                processResponse = function (transport) {
 
2246
                        this.setImageProperties(transport.responseJSON);
 
2247
                        
 
2248
                        var hv = this.viewport.controller;
 
2249
                        
 
2250
                        // update viewport sandbox if necessary
 
2251
                        this.viewport.updateSandbox();
 
2252
 
 
2253
                        // Add to tileLayer Accordion if it's not already there
 
2254
                        if (!hv.tileLayerAccordion.hasId(this.id)) {
 
2255
                                hv.tileLayerAccordion.addLayer(this);
 
2256
                        }
 
2257
                        // Otherwise update the accordion entry information
 
2258
                        else {
 
2259
                                hv.tileLayerAccordion.updateTimeStamp(this);
 
2260
                                hv.tileLayerAccordion.updateLayerDesc(this.id, this.name);
 
2261
                                hv.tileLayerAccordion.updateOpacitySlider(this.id, this.opacity);
 
2262
                        }
 
2263
                };
 
2264
                
 
2265
                // Ajax request
 
2266
                xhr = new Ajax.Request(this.viewport.controller.imageAPI, {
 
2267
                        method: 'POST',
 
2268
                        parameters: {
 
2269
                                action: 'getClosestImage',
 
2270
                                observatory: this.observatory,
 
2271
                                instrument:  this.instrument,
 
2272
                                detector:    this.detector,
 
2273
                                measurement: this.measurement,
 
2274
                                timestamp:   date.getTime() / 1000,
 
2275
                                debug: false                            
 
2276
                        },
 
2277
                        onSuccess: processResponse.bind(this)
 
2278
                });
 
2279
        },
 
2280
        
 
2281
        /**
 
2282
         * @description Toggle image sharpening
 
2283
         */
 
2284
        toggleSharpening: function () {
 
2285
                if (this.sharpen === true) {
 
2286
                        
 
2287
                } else {
 
2288
                        //jQuery(this.domNode.childElements());
 
2289
                        //jQuery("img.tile[src!=images/transparent_512.gif]").pixastic("sharpen", {amount: 0.35});
 
2290
                }
 
2291
                this.sharpen = !this.sharpen;
 
2292
        },
 
2293
 
 
2294
        /**
 
2295
         * @description Check to see if all visible tiles have been loaded
 
2296
         * @param {Object} position Position
 
2297
         */
 
2298
        viewportMove: function (position) {
 
2299
                var visible = this.viewport.visible,
 
2300
                        indices = this.viewport.visibleRange,
 
2301
                        i, j;
 
2302
 
 
2303
                //console.log("Checking tiles from " + indices.xStart + " to " + indices.xEnd);
 
2304
 
 
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]) {
 
2308
                                        this.tiles[i] = [];
 
2309
                                }
 
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)));
 
2313
                                }
 
2314
                        }
 
2315
                }
 
2316
        },
 
2317
 
 
2318
        /**
 
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
 
2323
         */
 
2324
        getTile: function (x, y) {
 
2325
                var left = x * this.tileSize,
 
2326
                        top  = y * this.tileSize,
 
2327
                        zoom  = this.viewport.zoomLevel,
 
2328
                        ts = this.tileSize,
 
2329
                        rf = function () {
 
2330
                                return false;
 
2331
                        }, img;
 
2332
                        
 
2333
                img = $(new Image());
 
2334
                img.addClassName('tile');
 
2335
                img.setStyle({
 
2336
                        left: left + 'px',
 
2337
                        top: top + 'px'
 
2338
                });
 
2339
                img.unselectable = 'on';
 
2340
 
 
2341
                img.onmousedown   = rf;
 
2342
                img.ondrag        = rf;
 
2343
                img.onmouseover   = rf;
 
2344
                img.oncontextmenu = rf;
 
2345
                img.galleryimg    = 'no';
 
2346
                img.alt           = "";
 
2347
 
 
2348
                
 
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'; 
 
2352
                });
 
2353
 
 
2354
                // Load tile
 
2355
                img.src = this.tileAPI + '?action=getTile&x=' + x + '&y=' + y + '&zoom=' + zoom + '&imageId=' + this.imageId + '&ts=' + ts;
 
2356
                
 
2357
                return img;
 
2358
        }
 
2359
});
 
2360
/**
 
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
 
2366
 */
 
2367
/*global TileLayerAccordion, Class, jQuery, Ajax, Layer, $, $$, $A, $R, Control, Element, TileLayer, Event, Hash */
 
2368
var TileLayerAccordion = Class.create(Layer,
 
2369
        /** @lends TileLayerAccordion.prototype */
 
2370
        {
 
2371
        /**
 
2372
         * @constructs
 
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
 
2376
         */
 
2377
        initialize: function (layerManager, containerId) {
 
2378
                this.layerManager = layerManager;
 
2379
                this.container =    jQuery('#' + containerId);
 
2380
                this.queryURL =     "api/index.php";
 
2381
 
 
2382
                this.options = {};
 
2383
 
 
2384
                //Setup menu UI components
 
2385
                this._setupUI();
 
2386
 
 
2387
                //Initialize accordion
 
2388
                this.domNode = jQuery('#TileLayerAccordion-Container');
 
2389
                this.domNode.dynaccordion({startClosed: true});
 
2390
                
 
2391
                //Individual layer menus
 
2392
                this.layerSettings = new Hash();
 
2393
        },
 
2394
 
 
2395
        /**
 
2396
         * @description Adds a new entry to the tile layer accordion
 
2397
         * @param {Object} layer The new layer to add
 
2398
         */
 
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;
 
2405
                        
 
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>";
 
2409
 
 
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;                       
 
2415
                        
 
2416
                        // Create accordion entry body
 
2417
                        body = this._buildEntryBody(layer);
 
2418
 
 
2419
                        //var startOpened = (this.layerManager.numTileLayers() > 1);
 
2420
 
 
2421
                        //Add to accordion
 
2422
                        this.domNode.dynaccordion("addSection", {
 
2423
                                id:     layer.id,
 
2424
                                header: head,
 
2425
                                cell:   body,
 
2426
                                open:   layer.startOpened
 
2427
                        });
 
2428
                        
 
2429
                        
 
2430
                        slider = new Control.Slider("opacity-slider-handle-" + layer.id, "opacity-slider-track-" + layer.id, {
 
2431
                                sliderValue: layer.opacity,
 
2432
                                range:       $R(1, 100),
 
2433
                                values:      $R(1, 100),
 
2434
                                onSlide:     function (v) {
 
2435
                                        layer.setOpacity(v);    
 
2436
                                }
 
2437
                        });
 
2438
                        
 
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) {
 
2444
                                        var val = ui.value;
 
2445
                                        layer.setOpacity(val);                                  
 
2446
                                }
 
2447
                        });*/
 
2448
                        
 
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, {
 
2452
                                header: head,
 
2453
                                body: body,
 
2454
                                opacitySlider: slider
 
2455
                        });
 
2456
                        
 
2457
                        // Event-handlers
 
2458
                        this._setupEventHandlers(layer);
 
2459
                        
 
2460
                        // Update timestamp
 
2461
                        this.updateTimeStamp(layer);
 
2462
                },
 
2463
                
 
2464
                //Ajax Request
 
2465
                xhr = new Ajax.Request(this.queryURL, {
 
2466
                        method: 'POST',
 
2467
                        onSuccess: processResponse.bind(this),
 
2468
                        parameters: {
 
2469
                                action     : "getLayerAvailability",
 
2470
                                observatory: layer.observatory,
 
2471
                                instrument:  layer.instrument,
 
2472
                                detector:    layer.detector,
 
2473
                                measurement: layer.measurement,
 
2474
                                format:      "json"
 
2475
                        }
 
2476
                });
 
2477
        },
 
2478
 
 
2479
        /**
 
2480
         * @description Checks to see if the given layer is listed in the accordion
 
2481
         * @param {String} id ID of the layer being checked 
 
2482
         */
 
2483
        hasId: function (id) {
 
2484
                return (this.layerSettings.keys().grep(id).length > 0 ? true : false);
 
2485
        },
 
2486
        
 
2487
        /**
 
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.
 
2491
         */
 
2492
        _buildEntryBody: function (layer) {
 
2493
                var id, options, opacitySlide, obs, inst, det, meas, fits;
 
2494
                
 
2495
                id = layer.id;
 
2496
                options = this.options;
 
2497
                
 
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>";
 
2503
                                
 
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'";
 
2511
                        }                                
 
2512
                        obs += ">" + o.name + "</option>";                      
 
2513
                });
 
2514
                obs += "</select><br>";
 
2515
                
 
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'";
 
2523
                        }
 
2524
                        inst += ">" + o.name + "</option>";                     
 
2525
                });
 
2526
                inst += "</select><br>";
 
2527
                
 
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'";
 
2535
                        }
 
2536
                        det += ">" + (o.name === "" ? o.abbreviation : o.name) + "</option>";           
 
2537
                });
 
2538
                det += "</select><br>";
 
2539
                
 
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'";
 
2547
                        }
 
2548
                        meas += ">" + o.name + "</option>";             
 
2549
                });
 
2550
                meas += "</select><br><br>";
 
2551
                
 
2552
                fits = "<a href='#' id='showFITSBtn-" + id + "' style='margin-left:160px; color: white; text-decoration: none;'>FITS Header</a><br>";
 
2553
                
 
2554
                return (opacitySlide + obs + inst + det + meas + fits);
 
2555
        },
 
2556
        
 
2557
        //_addOpacitySlider: function (layer) {
 
2558
        //      
 
2559
        //},
 
2560
        
 
2561
        /**
 
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
 
2565
         */
 
2566
        updateOpacitySlider: function (id, opacity) {
 
2567
                this.layerSettings.get(id).opacitySlider.setValue(opacity);
 
2568
        },
 
2569
 
 
2570
        /**
 
2571
         * @description Handles setting up an empty tile layer accordion.
 
2572
         */
 
2573
        _setupUI: function () {
 
2574
                var title, addLayerBtn, hv, self = this;
 
2575
                
 
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>'));
 
2581
                
 
2582
        // Event-handlers
 
2583
                hv = this.layerManager.controller;
 
2584
        addLayerBtn.click(function () {
 
2585
                        self.layerManager.addNewLayer();
 
2586
        });
 
2587
        },
 
2588
 
 
2589
        /**
 
2590
         * @description Sets up event-handlers for a TileLayerAccordion entry
 
2591
         * @param {Object} layer The layer being added
 
2592
         */
 
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);
 
2598
 
 
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();
 
2605
                };
 
2606
 
 
2607
                // Function for handling layer remove button
 
2608
                removeLayer = function (e) {
 
2609
                        accordion = e.data;
 
2610
                        accordion.layerManager.removeLayer(layer);
 
2611
                        accordion.domNode.dynaccordion('removeSection', {id: layer.id});
 
2612
                        accordion.layerSettings.unset(layer.id);
 
2613
                        accordion.layerManager.refreshSavedTileLayers();
 
2614
 
 
2615
                        //accordion.layers = accordion.layers.without(layer.id);
 
2616
                        e.stopPropagation();
 
2617
                };
 
2618
                
 
2619
                // Function for handling requests to display FITS header Info
 
2620
                showFITS = function () {
 
2621
                        dialogId = "fits-header-" + layer.id;
 
2622
                        
 
2623
                        // Check to see if a dialog already exists
 
2624
                        if (jQuery("#" + dialogId).length === 0) {
 
2625
                        
 
2626
                                // Ajax Responder
 
2627
                                processResponse = function (transport) {
 
2628
                                        response = transport.responseJSON;
 
2629
                                                
 
2630
                                        // Format results
 
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>";
 
2634
                                        });
 
2635
                                        formatted += "</div></div>";
 
2636
        
 
2637
                                        jQuery("body").append(formatted);
 
2638
                                        jQuery("#" + dialogId).dialog({
 
2639
                                                autoOpen: true,
 
2640
                                                title: "FITS Header: " + layer.name,
 
2641
                                                width: 400,
 
2642
                                                height: 350,
 
2643
                                                draggable: true
 
2644
                                        });
 
2645
                                };
 
2646
                                
 
2647
                                // Ajax Request
 
2648
                                xhr = new Ajax.Request("api/index.php", {
 
2649
                                        method: 'POST',
 
2650
                                        onSuccess: processResponse.bind(this),
 
2651
                                        parameters: {
 
2652
                                                action:  "getJP2Header",
 
2653
                                                imageId: layer.imageId
 
2654
                                        }
 
2655
                                });
 
2656
                                
 
2657
                        // If it does exist but is closed, open the dialog
 
2658
                        } else {
 
2659
                                if (!jQuery("#" + dialogId).dialog("isOpen")) {
 
2660
                                        jQuery("#" + dialogId).dialog("open");
 
2661
                                } else {
 
2662
                                        //jQuery("#" + dialogId).dialog("destroy");
 
2663
                                        jQuery("#" + dialogId).dialog("close");
 
2664
                                }
 
2665
                        }       
 
2666
                };
 
2667
                
 
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;
 
2674
                                }
 
2675
                                else if (this.name === "instrument") {
 
2676
                                        layer.instrument = this.value;
 
2677
                                }
 
2678
                                else if (this.name === "detector") {
 
2679
                                        layer.detector = this.value;
 
2680
                                }
 
2681
                                else if (this.name === "measurement") {
 
2682
                                        layer.measurement = this.value;
 
2683
                                }
 
2684
                                
 
2685
                                // Validate new settings and reload layer
 
2686
                                self._onLayerSelectChange(layer, this.name, this.value);
 
2687
                        });
 
2688
                });
 
2689
 
 
2690
                // Display FITS header
 
2691
                fitsBtn.bind('click', this, showFITS);
 
2692
 
 
2693
                //visibilityBtn.click(toggleVisibility);
 
2694
                visibilityBtn.bind('click', this, toggleVisibility);
 
2695
                removeBtn.bind('click', this, removeLayer);
 
2696
        },
 
2697
        
 
2698
        /**
 
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
 
2705
         */
 
2706
        _onLayerSelectChange: function (layer, changed, value) {
 
2707
                var obs, inst, det, meas, xhr, processResponse;
 
2708
                
 
2709
                // Ajax callback function
 
2710
                processResponse = function (transport) {
 
2711
                        // Update options
 
2712
                        this.options = transport.responseJSON;
 
2713
 
 
2714
                        // Case 1: Observatory changed
 
2715
                        if (changed === "observatory") {
 
2716
                                this._updateOptions(layer.id, "instrument", this.options.instruments);
 
2717
                                
 
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];
 
2721
                                }
 
2722
                        }
 
2723
                        
 
2724
                        // Case 2: Instrument changed
 
2725
                        if ((changed === "observatory") || (changed === "instrument")) {
 
2726
                                this._updateOptions(layer.id, "detector", this.options.detectors);
 
2727
                                
 
2728
                                //Make sure the detector choice is still valid.
 
2729
                                if (!$A(this.options.detectors).find(function (det) {
 
2730
                                return det.abbreviation === layer.detector;
 
2731
                                })) {
 
2732
                                        layer.detector = this.options.detectors[0].abbreviation;
 
2733
                                }
 
2734
                        }
 
2735
                        
 
2736
                        // Case 3: Detector changed
 
2737
                        if ((changed === "observatory") || (changed === "instrument") || (changed === "detector")) {
 
2738
                                this._updateOptions(layer.id, "measurement", this.options.measurements);        
 
2739
                                
 
2740
                                //Make sure the measurement choice is still valid.
 
2741
                                if (!$A(this.options.measurements).find(function (meas) {
 
2742
                                return meas.abbreviation === layer.measurement;
 
2743
                                })) {
 
2744
                                        layer.measurement = this.options.measurements[0].abbreviation;
 
2745
                                }
 
2746
                                
 
2747
                                
 
2748
                                //if ($A(this.options.measurements).grep(layer.measurement).length == 0) {
 
2749
                                //      layer.measurement = this.options.measurements[0];
 
2750
                                //}
 
2751
                                /*
 
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];
 
2755
                                        
 
2756
                                        //update selectedIndex
 
2757
                                        var self = this;
 
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;
 
2761
                                                }
 
2762
                                        });
 
2763
                                }*/
 
2764
                        }
 
2765
                        
 
2766
                        //reload layer settings
 
2767
                        layer.reload();
 
2768
                        
 
2769
                        // Update stored user settings
 
2770
                        this.layerManager.refreshSavedTileLayers();
 
2771
                };
 
2772
                
 
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);
 
2780
                        
 
2781
                        // Ajax Request
 
2782
                        xhr = new Ajax.Request(this.queryURL, {
 
2783
                                method: 'POST',
 
2784
                                onSuccess: processResponse.bind(this),
 
2785
                                parameters: {
 
2786
                                        action: "getLayerAvailability",
 
2787
                                        observatory: obs,
 
2788
                                        instrument: inst,
 
2789
                                        detector: det,
 
2790
                                        measurement: meas,
 
2791
                                        format: "json",
 
2792
                                        changed: changed,
 
2793
                                        value: value
 
2794
                                }
 
2795
                        });
 
2796
                }
 
2797
                else {
 
2798
                        //reload layer settings
 
2799
                        layer.reload();
 
2800
                        
 
2801
                        // Update stored user settings
 
2802
                        this.layerManager.refreshSavedTileLayers();
 
2803
                }
 
2804
        },
 
2805
        
 
2806
        /**
 
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
 
2811
         */
 
2812
        _updateOptions: function (id, field, newOptions) {
 
2813
                var select, opt;
 
2814
                
 
2815
                //Remove old options
 
2816
                $$('#' + field + '-select-' + id + ' > option').each(function (o) {
 
2817
                        o.remove();
 
2818
                });
 
2819
                
 
2820
                //Add new options
 
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);
 
2824
                        select.insert(opt);
 
2825
                });
 
2826
                
 
2827
        },
 
2828
    
 
2829
    /**
 
2830
     * @description Updates the displayed timestamp for a given tile layer
 
2831
     * @param {Object} layer The layer being updated
 
2832
     */
 
2833
    updateTimeStamp: function (layer) {
 
2834
                var domNode, date, dateString, timeDiff, ts;
 
2835
                
 
2836
        //Grab timestamp dom-node
 
2837
        domNode = $(layer.id).select('.timestamp').first();
 
2838
        
 
2839
        //remove any pre-existing styling
 
2840
        domNode.removeClassName("timeBehind");
 
2841
        domNode.removeClassName("timeAhead");
 
2842
        domNode.removeClassName("timeSignificantlyOff");
 
2843
                
 
2844
        // Update the timestamp
 
2845
        date = new Date(layer.timestamp * 1000);
 
2846
        dateString = date.toYmdUTCString() + ' ' + date.toHmUTCString();
 
2847
 
 
2848
        // Calc the time difference
 
2849
        timeDiff = layer.timestamp - this.layerManager.controller.date.getTime() / 1000;
 
2850
 
 
2851
        //this.domNode.select(".timestamp").first().update(dateString + ' ' + timeDiffStr);
 
2852
        domNode.update(dateString);
 
2853
        
 
2854
        //get timestep (TODO: create a better accessor)
 
2855
        //var ts = this.layerManager.controller.timeStepSlider.timestep.numSecs;
 
2856
                ts = this.layerManager.controller.timeIncrementSecs;
 
2857
        
 
2858
        // Check to see if observation times match the actual time
 
2859
        if (timeDiff < 0) {
 
2860
                if (Math.abs(timeDiff) > (4 * ts)) {
 
2861
                        domNode.addClassName("timeSignificantlyOff");
 
2862
                }
 
2863
                else {
 
2864
                        domNode.addClassName("timeBehind");
 
2865
                }
 
2866
        }
 
2867
        else if (timeDiff > 0) {
 
2868
                if (timeDiff > (4 * ts)) {
 
2869
                        domNode.addClassName("timeSignificantlyOff");
 
2870
                }
 
2871
                else {
 
2872
                        domNode.addClassName("timeAhead");
 
2873
                }
 
2874
        }
 
2875
    },
 
2876
        
 
2877
        /**
 
2878
         * @description Updates the description for a given tile layer
 
2879
         * @param {String} id Layer id
 
2880
         * @param {String} desc New description to use 
 
2881
         */
 
2882
        updateLayerDesc: function (id, desc) {
 
2883
                $(id).select("span.tile-accordion-header-left").first().update(desc);
 
2884
        }    
 
2885
});
 
2886
 
 
2887
/**
 
2888
 * @fileOverview Contains the class definition for an TimeControls class.
 
2889
 * Syntax: Prototype
 
2890
 * @author <a href="mailto:keith.hughitt@gmail.com">Keith Hughitt</a>
 
2891
 */
 
2892
/*global TimeControls, Class, UIElement, Event, Element, $, $A */
 
2893
var TimeControls = Class.create(UIElement,
 
2894
        /** @lends TimeControls.prototype */
 
2895
        {
 
2896
    /**
 
2897
     * @constructs
 
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
 
2904
     */
 
2905
    initialize : function (controller, incrementSelect, backBtn, forwardBtn, timeIncrement) {
 
2906
        this.controller = controller;
 
2907
        
 
2908
        //Private member variables
 
2909
        this.className = "TimeControls";
 
2910
        
 
2911
        // Set increment 
 
2912
        this.timeIncrement = timeIncrement;
 
2913
                
 
2914
                // Populate select box
 
2915
                this.addTimeIncrements(incrementSelect);
 
2916
        
 
2917
        // Event-handlers
 
2918
        Event.observe(backBtn,    'click', this.timePrevious.bind(this));
 
2919
        Event.observe(forwardBtn, 'click', this.timeNext.bind(this));
 
2920
    },
 
2921
    
 
2922
    /**
 
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
 
2925
     */
 
2926
    addTimeIncrements: function (selectId) {
 
2927
                var timeSteps, select, opt;
 
2928
                
 
2929
        timeSteps = [
 
2930
            {numSecs: 1,       txt: "1&nbsp;Sec"},
 
2931
            {numSecs: 60,      txt: "1&nbsp;Min"},
 
2932
            {numSecs: 300,     txt: "5&nbsp;Mins"},
 
2933
            {numSecs: 900,     txt: "15&nbsp;Mins"},
 
2934
            {numSecs: 3600,    txt: "1&nbsp;Hour"},
 
2935
            {numSecs: 21600,   txt: "6&nbsp;Hours"},
 
2936
            {numSecs: 43200,   txt: "12&nbsp;Hours"},
 
2937
            {numSecs: 86400,   txt: "1&nbsp;Day"},
 
2938
            {numSecs: 604800,  txt: "1&nbsp;Week"},
 
2939
            {numSecs: 2419200, txt: "28&nbsp;Days"}
 
2940
        ];
 
2941
        
 
2942
                select = $(selectId);
 
2943
                
 
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);
 
2947
                        select.insert(opt);
 
2948
                });
 
2949
                
 
2950
                // Select default timestep
 
2951
                select.select('option[value=' + this.timeIncrement + ']')[0].writeAttribute('selected', 'selected');
 
2952
                
 
2953
                // Event-handler
 
2954
                Event.observe(select, 'change', this.onChange.bindAsEventListener(this));
 
2955
    },
 
2956
    
 
2957
   /**
 
2958
    * @description Time-incremenet change event handler
 
2959
    * @param {Event} e Prototype Event Object
 
2960
    */
 
2961
    onChange: function (e) {
 
2962
                this.timeIncrement = parseInt(e.target.value, 10);
 
2963
                this.fire('timeIncrementChange', this.timeIncrement);
 
2964
    },
 
2965
      
 
2966
   /**
 
2967
    * @description Move back one time incremement
 
2968
    */
 
2969
    timePrevious: function () {
 
2970
        var newDate = this.controller.date.addSeconds(-this.timeIncrement);
 
2971
        this.controller.setDate(newDate);
 
2972
        this.controller.calendar.updateFields();
 
2973
    },
 
2974
    
 
2975
    /**
 
2976
     * @function Move forward one time increment
 
2977
     */
 
2978
    timeNext: function () {
 
2979
        var newDate = this.controller.date.addSeconds(this.timeIncrement);
 
2980
        this.controller.setDate(newDate);
 
2981
        this.controller.calendar.updateFields();
 
2982
    } 
 
2983
});
 
2984
/**
 
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>
 
2988
 */
 
2989
/*global Class, CookieJar, $H */
 
2990
var UserSettings = Class.create(
 
2991
        /** @lends UserSettings.prototype */
 
2992
        {
 
2993
        /**
 
2994
         * @constructs
 
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
 
2998
         */
 
2999
        initialize: function (controller) {
 
3000
                this.controller = controller;
 
3001
                
 
3002
                /**
 
3003
                 * @description Default user settings
 
3004
                 */
 
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,
 
3011
                        'event-icons'           : {
 
3012
                                'VSOService::noaa':                             'small-blue-circle',
 
3013
                                'GOESXRayService::GOESXRay':    'small-green-diamond',
 
3014
                                'VSOService::cmelist':                  'small-yellow-square'
 
3015
                        }
 
3016
                });
 
3017
                
 
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']);
 
3021
 
 
3022
 
 
3023
                this.cookies = new CookieJar({
 
3024
                        expires: 31536000, //1 year
 
3025
                        path: '/'
 
3026
                });
 
3027
                
 
3028
                if (!this._exists()) {
 
3029
                        this._loadDefaults();
 
3030
                }
 
3031
        },
 
3032
        
 
3033
        /**
 
3034
         * @description Saves a specified setting
 
3035
         * @param {String} key The setting to update
 
3036
         * @param {JSON} value The new value for the setting
 
3037
         */
 
3038
        set: function (key, value) {
 
3039
                if (this._validate(key, value)) {
 
3040
                        this.cookies.put(key, value);
 
3041
                } else {
 
3042
                        //console.log("Ignoring invalid user-setting...");
 
3043
                }
 
3044
        },
 
3045
        
 
3046
        /**
 
3047
         * @description Gets a specified setting
 
3048
         * @param {String} key The setting to retrieve
 
3049
         * @returns {JSON} The value of the desired setting
 
3050
         */
 
3051
        get: function (key) {
 
3052
                // Parse numeric types
 
3053
                if (this._INTEGER_PARAMS.include(key)) {
 
3054
                        return parseInt(this.cookies.get(key));
 
3055
                }
 
3056
                else if (this._FLOAT_PARAMS.include(key)) {
 
3057
                        return parseFloat(this.cookies.get(key));
 
3058
                }
 
3059
                else if (this._BOOLEAN_PARAMS.include(key)) {
 
3060
                        return this.cookies.get(key) == "true" ? true : false;
 
3061
                }
 
3062
                return this.cookies.get(key);
 
3063
        },
 
3064
        
 
3065
        /**
 
3066
         * @description Checks to see if user-settings cookies have been set.
 
3067
         */
 
3068
        _exists: function () {
 
3069
                return (this.cookies.getKeys().length > 0);
 
3070
        },
 
3071
        
 
3072
        /**
 
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
 
3076
         */
 
3077
        _validate: function (setting, value) {
 
3078
                switch (setting) {
 
3079
                case "obs-date":
 
3080
                        if (isNaN(value)) {
 
3081
                                return false;
 
3082
                        }
 
3083
                        break;
 
3084
                case "zoom-level":
 
3085
                        if ((isNaN(value)) || (value < this.controller.minZoomLevel) || (value > this.controller.maxZoomLevel)) {
 
3086
                                return false;
 
3087
                        }
 
3088
                        break;
 
3089
                default:
 
3090
                        break;          
 
3091
                }
 
3092
                return true;
 
3093
        },
 
3094
        
 
3095
        /**
 
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
 
3098
         */
 
3099
        _resetSetting: function (setting) {
 
3100
                this.set(setting, this._getDefault(setting));           
 
3101
        },
 
3102
        
 
3103
        /**
 
3104
         * @description Gets the default value for a given setting
 
3105
         * @param {Object} setting
 
3106
         */
 
3107
        _getDefault: function (setting) {
 
3108
                return this._DEFAULTS.get(setting);
 
3109
        },
 
3110
        
 
3111
        /**
 
3112
         * @description Loads defaults if cookies have not been set prior.
 
3113
         */
 
3114
        _loadDefaults: function () {
 
3115
                var self = this;
 
3116
                this._DEFAULTS.each(function (setting) {
 
3117
                        self.set(setting.key, setting.value);
 
3118
                });
 
3119
        }
 
3120
});
 
3121
/**
 
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
 
3126
 */
 
3127
/*global Class, UIElement, $, Builder, Element, ViewportHandlers, document */
 
3128
var Viewport = Class.create(UIElement, 
 
3129
        /** @lends Viewport.prototype */
 
3130
        {
 
3131
        /**
 
3132
         * @description Default Viewport settings
 
3133
         */ 
 
3134
        defaultOptions: {
 
3135
                zoomLevel: 0,
 
3136
                headerId: 'middle-col-header',
 
3137
                footerId: 'footer',
 
3138
                tileSize:  512,
 
3139
                minHeight: 450,
 
3140
                debug:     false,
 
3141
                prefetch:  0  //Pre-fetch any tiles that fall within this many pixels outside the physical viewport
 
3142
        },
 
3143
        isMoving: false,
 
3144
        dimensions: { width: 0, height: 0 },
 
3145
 
 
3146
        /**
 
3147
         * @constructs
 
3148
         * @description Creates a new Viewport
 
3149
         * @param {Object} controller A Reference to the Helioviewer application class
 
3150
         * @param {Object} options Custom Viewport settings
 
3151
         * <br>
 
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>
 
3160
         * </div>
 
3161
         */
 
3162
        initialize: function (controller, options) {
 
3163
                Object.extend(this, this.defaultOptions);
 
3164
                Object.extend(this, options);
 
3165
                
 
3166
                var center, centerBox;
 
3167
 
 
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);
 
3174
 
 
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;
 
3177
 
 
3178
                // Resize to fit screen
 
3179
                this.resize();
 
3180
                
 
3181
                // Determine center of viewport
 
3182
                center = this.getCenter();
 
3183
                
 
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'});
 
3187
                
 
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});
 
3191
 
 
3192
                // For Debugging purposes only
 
3193
                if (this.debug) {
 
3194
                        this.movingContainer.setStyle({'border': '1px solid red'});
 
3195
                        
 
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);
 
3199
                }
 
3200
        },
 
3201
        
 
3202
        /**
 
3203
         * @description Centers the viewport.
 
3204
         */
 
3205
        center: function () {
 
3206
                //var center = this.getCenter();
 
3207
                var sb = this.sandbox.getDimensions();
 
3208
                
 
3209
                this.moveTo(0.5 * sb.width, 0.5 * sb.height);
 
3210
        },
 
3211
 
 
3212
        /**
 
3213
         * @description Move the viewport focus to a new location.
 
3214
         * @param {Int} x X-value
 
3215
         * @param {Int} y Y-value
 
3216
         */
 
3217
        moveTo: function (x, y) {
 
3218
                this.movingContainer.setStyle({
 
3219
                        left: x + 'px',
 
3220
                        top:  y + 'px'    
 
3221
                });
 
3222
                
 
3223
                this.checkTiles();
 
3224
                this.fire('move', { x: x, y: y });
 
3225
        },
 
3226
 
 
3227
        /**
 
3228
         * @description Moves the viewport's focus
 
3229
         * @param {Int} x X-value
 
3230
         * @param {Int} y Y-value
 
3231
         */   
 
3232
        moveBy: function (x, y) {
 
3233
                // Sandbox dimensions
 
3234
                var sandbox = this.sandbox.getDimensions(),
 
3235
                
 
3236
                pos = {
 
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)
 
3239
                };
 
3240
                
 
3241
                this.movingContainer.setStyle({
 
3242
                        left: pos.x + 'px',
 
3243
                        top:  pos.y + 'px'    
 
3244
                });
 
3245
                
 
3246
                this.checkTiles();
 
3247
                this.fire('move', { x: pos.x, y: pos.y });
 
3248
        },
 
3249
        
 
3250
        /**
 
3251
         * @description Event-handler for a mouse-drag start.
 
3252
         */
 
3253
        startMoving: function () {
 
3254
                this.startMovingPosition = this.getContainerPos();
 
3255
        },
 
3256
        
 
3257
        /**
 
3258
         * @description Get the coordinates of the viewport center
 
3259
         * @returns {Object} The X & Y coordinates of the viewport's center
 
3260
         */
 
3261
        getCenter: function () {
 
3262
                return {
 
3263
                        x: Math.round(this.domNode.getWidth()  / 2),
 
3264
                        y: Math.round(this.domNode.getHeight() / 2)
 
3265
                };
 
3266
        },
 
3267
        
 
3268
        /**
 
3269
         * @description Get the current coordinates of the moving container
 
3270
         * @returns {Object} The X & Y coordinates of the viewport's top-left corner
 
3271
         */
 
3272
        getContainerPos: function () {
 
3273
                return {
 
3274
                        x: parseInt(this.movingContainer.getStyle('left'), 10),
 
3275
                        y: parseInt(this.movingContainer.getStyle('top'), 10)
 
3276
                };
 
3277
        },
 
3278
        
 
3279
        /**
 
3280
         * @description Alias for getContainerPos function
 
3281
         * @returns {Object} The X & Y coordinates of the viewport's top-left corner
 
3282
         */
 
3283
        currentPosition: function () {
 
3284
                return this.getContainerPos();
 
3285
        },
 
3286
        
 
3287
        /**
 
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
 
3290
         */
 
3291
        helioCenter: function () {
 
3292
                return this.getContainerPos();
 
3293
        },
 
3294
 
 
3295
        /**
 
3296
         * @description Event handler fired after dragging
 
3297
         */
 
3298
        endMoving: function () {
 
3299
        },
 
3300
        
 
3301
        /**
 
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.
 
3305
         */
 
3306
        checkTiles: function () {
 
3307
                var i, j, indices;
 
3308
                
 
3309
                this.visible = [];
 
3310
                
 
3311
                indices = this.displayRange();
 
3312
                
 
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] = [];
 
3318
                                }
 
3319
                                this.visible[i][j] = true;
 
3320
                        }
 
3321
                }
 
3322
        },
 
3323
        
 
3324
        /**
 
3325
         * @description Update the size and location of the movement-constraining box.
 
3326
         */
 
3327
        updateSandbox: function () {
 
3328
                var maxDimensions, old, center, newSize, change, movingContainerOldPos, newHCLeft, newHCTop, padHeight, shiftTop;
 
3329
                
 
3330
                this.dimensions = this.domNode.getDimensions();
 
3331
                maxDimensions   = this.controller.layerManager.getMaxDimensions();
 
3332
                old             = this.sandbox.getDimensions();
 
3333
                center          = this.getCenter();
 
3334
                
 
3335
                // New sandbox dimensions
 
3336
                newSize = {
 
3337
                        width : Math.max(0, maxDimensions.width  - this.dimensions.width),
 
3338
                        height: Math.max(0, maxDimensions.height - this.dimensions.height)
 
3339
                };
 
3340
                
 
3341
                if (this.debug) {
 
3342
                        $('vp-debug-center').setStyle({'left': center.x - 25 + 'px', 'top': center.y - 25 + 'px'});
 
3343
                }
 
3344
        
 
3345
                // Difference
 
3346
                change = {
 
3347
                        x: newSize.width  - old.width,
 
3348
                        y: newSize.height - old.height
 
3349
                };
 
3350
                
 
3351
                // Initial moving container position
 
3352
                movingContainerOldPos = this.movingContainer.positionedOffset();        
 
3353
                
 
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'                       
 
3360
                });
 
3361
                
 
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)));
 
3365
                
 
3366
                this.movingContainer.setStyle({
 
3367
                        left: newHCLeft + 'px',
 
3368
                        top : newHCTop  + 'px'
 
3369
                });
 
3370
        },
 
3371
        
 
3372
        /**
 
3373
         * @description Returns the range of indices for the tiles to be displayed.
 
3374
         * @returns {Object} The range of tiles which should be displayed
 
3375
         */
 
3376
        displayRange: function () {
 
3377
                var vp, ts;
 
3378
                
 
3379
                // Get heliocentric viewport coordinates
 
3380
                vp = this.getHCViewportPixelCoords();
 
3381
                
 
3382
                // Expand to fit tile increment
 
3383
                ts = this.tileSize;
 
3384
                vp = {
 
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)
 
3389
                };
 
3390
                
 
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
 
3397
                };
 
3398
                
 
3399
                return this.visibleRange;
 
3400
        },
 
3401
 
 
3402
        /**
 
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
 
3405
         */
 
3406
        getHCViewportPixelCoords: function () {
 
3407
                var sb, mc, vpDimensions;
 
3408
                
 
3409
                sb = this.sandbox.positionedOffset();
 
3410
                mc = this.movingContainer.positionedOffset();
 
3411
                vpDimensions = this.domNode.getDimensions();
 
3412
                
 
3413
                return {
 
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])
 
3418
                };
 
3419
        },
 
3420
 
 
3421
        /**
 
3422
         * @description Zooms To a specified zoom-level.
 
3423
         * @param {Int} zoomLevel The desired zoomLevel
 
3424
         */
 
3425
        zoomTo: function (zoomLevel) {
 
3426
                this.zoomLevel = zoomLevel;
 
3427
                
 
3428
                // reset the layers
 
3429
                this.checkTiles();
 
3430
                this.controller.layerManager.resetLayers(this.visible);
 
3431
        
 
3432
                // update sandbox
 
3433
                this.updateSandbox();
 
3434
                
 
3435
                // store new value
 
3436
                this.controller.userSettings.set('zoom-level', zoomLevel);
 
3437
        },
 
3438
 
 
3439
        /**
 
3440
         * @description Adjust viewport dimensions when window is resized.
 
3441
         */
 
3442
        resize: function () {
 
3443
                var oldDimensions, h, viewportOuter;
 
3444
                
 
3445
                // Get dimensions
 
3446
                oldDimensions = this.dimensions;
 
3447
                
 
3448
                // Ensure minimum height
 
3449
                h = Math.max(this.minHeight, document.viewport.getHeight() - this.headerAndFooterHeight);
 
3450
 
 
3451
                //Update viewport height
 
3452
                viewportOuter =  this.outerNode;
 
3453
                viewportOuter.setStyle({height: h + 'px'});
 
3454
 
 
3455
                this.dimensions = this.domNode.getDimensions();
 
3456
                
 
3457
                this.dimensions.width  += this.prefetch;
 
3458
                this.dimensions.height += this.prefetch;
 
3459
                
 
3460
                if (this.dimensions.width !== oldDimensions.width || this.dimensions.height !== oldDimensions.height) {
 
3461
                        if (this.controller.layerManager.layers.length > 0) {
 
3462
                                this.updateSandbox();
 
3463
                                this.checkTiles();
 
3464
                                this.controller.layerManager.resetLayers(this.visible);
 
3465
                        }
 
3466
                }
 
3467
        }
 
3468
});
 
3469
/**
 
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>
 
3473
 */
 
3474
/*global Class, document, window, Event, $, $$ */
 
3475
var ViewportHandlers = Class.create(
 
3476
        /** @lends ViewportHandlers.prototype */
 
3477
        {
 
3478
        startingPosition:      { x: 0, y: 0 },
 
3479
        mouseStartingPosition: { x: 0, y: 0 },
 
3480
        mouseCurrentPosition:  { x: 0, y: 0 },
 
3481
        mouseCoords  :         { x: 0, y: 0 },
 
3482
        moveCounter  : 0,
 
3483
        moveThrottle : 2,
 
3484
        naturalZoomLevel  : 10,
 
3485
        naturalResolution : 2.63,
 
3486
        rSunArcSeconds    : 975,
 
3487
 
 
3488
        /**
 
3489
         * @constructs
 
3490
         * @description Contains a collection of event-handlers for dealing with Viewport-related events
 
3491
         * @see Viewport
 
3492
         * @param {Object} viewport A Reference to the Helioviewer application class
 
3493
         */
 
3494
        initialize: function (viewport) {
 
3495
                this.viewport = viewport;
 
3496
 
 
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));
 
3503
 
 
3504
                // Double-clicks
 
3505
                Event.observe(this.viewport.domNode, 'dblclick', this.doubleClick.bindAsEventListener(this));
 
3506
 
 
3507
                // Mouse-wheel
 
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
 
3510
 
 
3511
                //Keyboard-related event-handlers
 
3512
                Event.observe(document, 'keypress', this.keyPress.bindAsEventListener(this));
 
3513
                Event.observe(window, 'keypress', this.keyPress.bindAsEventListener(this));
 
3514
        },
 
3515
 
 
3516
        /**
 
3517
         * @description Fired when a mouse is pressed
 
3518
         * @param {Event} event Prototype Event class
 
3519
         */
 
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)
 
3527
                };
 
3528
                this.viewport.domNode.setStyle({ cursor: 'all-scroll' });
 
3529
                if (this.viewport.domNode.setCapture) {
 
3530
                        this.viewport.domNode.setCapture();
 
3531
                }
 
3532
                this.viewport.startMoving();
 
3533
        },
 
3534
 
 
3535
        /**
 
3536
         * @description Handles double-clicks
 
3537
         * @param {Event} e Prototype Event class
 
3538
         */
 
3539
        doubleClick: function (e) {
 
3540
                var pos,
 
3541
                        viewport = this.viewport;
 
3542
                
 
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()) {
 
3546
                                
 
3547
                                pos = this.getRelativeCoords(e.pointerX(), e.pointerY());
 
3548
                                
 
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}));
 
3551
                                
 
3552
                                viewport.center();                              
 
3553
                                this.viewport.startMoving();
 
3554
 
 
3555
                                //adjust for zoom
 
3556
                                if (e.shiftKey) {
 
3557
                                        viewport.moveBy(0.5 * pos.x, 0.5 * pos.y);
 
3558
                                        viewport.controller.zoomControl.zoomButtonClicked(1);
 
3559
                                }
 
3560
                                else {
 
3561
                                        viewport.moveBy(2 * pos.x, 2 * pos.y);
 
3562
                                        viewport.controller.zoomControl.zoomButtonClicked(-1);
 
3563
                                }
 
3564
                        }
 
3565
                } else {
 
3566
                        //console.log("Out of bounds double-click request! See Viewport.js:57");
 
3567
                }
 
3568
        },
 
3569
        
 
3570
        /**
 
3571
         * @description Handles mouse-wheel movements
 
3572
         * @param {Event} event Prototype Event class
 
3573
         */
 
3574
        mouseWheel: function (e) {
 
3575
                this.viewport.controller.zoomControl.zoomButtonClicked(-Event.wheel(e));
 
3576
        },
 
3577
        
 
3578
        /**
 
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
 
3582
         */
 
3583
        getRelativeCoords: function (screenx, screeny) {
 
3584
                var vp, offset, mouseCoords;
 
3585
                
 
3586
                vp = this.viewport;
 
3587
                
 
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();
 
3592
 
 
3593
                // Mouse-coordinates relative to the top-left of the viewport
 
3594
                //var mouseCoords = {
 
3595
                //      x: screenx - xOffset,
 
3596
                //      y: screeny - yOffset
 
3597
                //}
 
3598
                mouseCoords = {
 
3599
                        x: screenx - offset[0] - 1,
 
3600
                        y: screeny - offset[1] - 1
 
3601
                };
 
3602
                return mouseCoords;
 
3603
        },
 
3604
 
 
3605
        /**
 
3606
         * @description Keyboard-related event-handlers
 
3607
         */
 
3608
        keyPress: function (e) {
 
3609
                var key = e.keyCode;
 
3610
 
 
3611
                //Ignore event if user is type in an input form field
 
3612
                if (e.target.tagName !== "INPUT") {
 
3613
 
 
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) {
 
3620
                                        return;
 
3621
                                }
 
3622
 
 
3623
                                //Right-arrow
 
3624
                                if (key === 37) {
 
3625
                                        this.viewport.moveBy(8, 0);
 
3626
                                }
 
3627
 
 
3628
                                //Up-arrow
 
3629
                                else if (e.keyCode === 38) {
 
3630
                                        this.viewport.moveBy(0, 8);
 
3631
                                }
 
3632
                                //Left-arrow
 
3633
                                else if (e.keyCode === 39) {
 
3634
                                        this.viewport.moveBy(-8, 0);
 
3635
                                }
 
3636
 
 
3637
                                //Down-arrow
 
3638
                                else if (e.keyCode === 40) {
 
3639
                                        this.viewport.moveBy(0, -8);
 
3640
                                }
 
3641
                        }
 
3642
                }
 
3643
        },
 
3644
 
 
3645
        /**
 
3646
         * @description Fired when a mouse button is released
 
3647
         * @param {Event} event Prototype Event object
 
3648
         */
 
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();
 
3655
                }
 
3656
                this.viewport.endMoving();
 
3657
        },
 
3658
 
 
3659
        /**
 
3660
         * @description Fired when a keyboard key is released
 
3661
         * @param {Object} event Prototype Event object
 
3662
         */
 
3663
        keyRelease: function (event) {
 
3664
                this.viewport.isMoving = false;
 
3665
                this.viewport.endMoving();
 
3666
        },
 
3667
 
 
3668
        /**
 
3669
         * @description Handle drag events
 
3670
         * @param {Object} event Prototype Event object
 
3671
         */
 
3672
        mouseMove: function (event) {
 
3673
                if (!this.viewport.isMoving) {
 
3674
                        return;
 
3675
                }
 
3676
                
 
3677
                var sb = this.viewport.sandbox.getDimensions();
 
3678
                if ((sb.width === 0) && (sb.height === 0)) {
 
3679
                        return;
 
3680
                }
 
3681
 
 
3682
                this.moveCounter = (this.moveCounter + 1) % this.moveThrottle;
 
3683
                if (this.moveCounter !== 0) {
 
3684
                        return;
 
3685
                }
 
3686
 
 
3687
                this.mouseCurrentPosition = {
 
3688
                        x: Event.pointerX(event), 
 
3689
                        y: Event.pointerY(event)
 
3690
                };
 
3691
 
 
3692
                this.viewport.moveBy(this.mouseStartingPosition.x - this.mouseCurrentPosition.x, this.mouseStartingPosition.y - this.mouseCurrentPosition.y);
 
3693
        },
 
3694
        
 
3695
        /**
 
3696
         * @description Toggles mouse-coords visibility
 
3697
         */
 
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();
 
3704
                }
 
3705
                // Case 2: Arcseconds -> Polar Coords
 
3706
                else if (this.viewport.mouseCoords === "arcseconds") {
 
3707
                        this.viewport.mouseCoords = "polar";
 
3708
                }
 
3709
                // Case 3: Polar Coords -> Disabled
 
3710
                else {
 
3711
                        $('mouse-coords').toggle();
 
3712
                        this.viewport.mouseCoords = "disabled";
 
3713
                        //console.log("Polar Coords -> Disabled");
 
3714
                }
 
3715
                
 
3716
                // Warn once
 
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);
 
3720
                }
 
3721
                
 
3722
                // Cartesian & Polar coords
 
3723
                if (this.viewport.mouseCoords !== "disabled") {
 
3724
                        mouseCoordsX = $('mouse-coords-x');
 
3725
                        mouseCoordsY = $('mouse-coords-y');
 
3726
                        
 
3727
                        // Clear old values
 
3728
                        mouseCoordsX.update("");
 
3729
                        mouseCoordsY.update("");
 
3730
                        
 
3731
                        // Remove existing event handler if switching from cartesian -> polar
 
3732
                        if (this.viewport.mouseCoords === "polar") {
 
3733
                                Event.stopObserving(this.viewport.movingContainer, "mousemove");
 
3734
                        }
 
3735
                        
 
3736
                        // Event-handler
 
3737
                        updateMouseCoords = function (e) {
 
3738
                                var VX, negSV, SV, SM, MX, scale, x, y, polar;
 
3739
                                                                
 
3740
                                // Store current mouse-coordinates
 
3741
                                self.mouseCoords = {x: e.pageX, y: e.pageY};
 
3742
                                
 
3743
                                // Threshold
 
3744
                                self.moveCounter = (self.moveCounter + 1) % self.moveThrottle;
 
3745
                                if (self.moveCounter !== 0) {
 
3746
                                        return;
 
3747
                                }
 
3748
                                
 
3749
                                // Coordinates realtive to viewport top-left corner
 
3750
                                VX = self.getRelativeCoords(e.pageX, e.pageY);
 
3751
                                negSV = self.viewport.sandbox.positionedOffset();
 
3752
                                SV = {
 
3753
                                        x: -negSV[0],
 
3754
                                        y: -negSV[1]
 
3755
                                };
 
3756
                                SM = $$('.movingContainer')[0].positionedOffset();                              
 
3757
                                MX = {
 
3758
                                        x: VX.x + (SV.x - SM[0]),
 
3759
                                        y: VX.y + (SV.y - SM[1])
 
3760
                                };
 
3761
                                
 
3762
                                //scale
 
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));
 
3766
                                
 
3767
                                // Arc-seconds
 
3768
                                if (self.viewport.mouseCoords === "arcseconds") {
 
3769
                                        mouseCoordsX.update("x: " + x + " &prime;&prime;");
 
3770
                                        mouseCoordsY.update("y: " + y + " &prime;&prime;");
 
3771
                                        
 
3772
                                // Polar coords
 
3773
                                } else {
 
3774
                                        polar = Math.toPolarCoords(x, -y);      
 
3775
                                
 
3776
                                        mouseCoordsX.update(((polar.r / self.rSunArcSeconds) + "").substring(0, 5) + " R<span style='vertical-align: sub; font-size:10px;'>&#9737;</span>");
 
3777
                                        mouseCoordsY.update(Math.round(polar.theta) + " &#176;");
 
3778
                                }
 
3779
                        };      
 
3780
                        Event.observe(this.viewport.movingContainer, "mousemove", updateMouseCoords);
 
3781
                        
 
3782
                        // Execute handler once immediately to show new coords
 
3783
                        updateMouseCoords({pageX: this.mouseCoords.x, pageY: this.mouseCoords.y});
 
3784
                        
 
3785
                } else {
 
3786
                        Event.stopObserving(this.viewport.movingContainer, "mousemove");
 
3787
                }
 
3788
        }
 
3789
});
 
3790
/**
 
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.
 
3796
 */
 
3797
/*global UIElement, Class, Control, Event, $R, $ */
 
3798
var ZoomControl = Class.create(UIElement,
 
3799
        /** @lends ZoomControl.prototype */
 
3800
        {
 
3801
        /**
 
3802
         * @constructs
 
3803
         * @description Creates a new ZoomControl
 
3804
         * @param {Object} controller A Reference to the Helioviewer application class
 
3805
         * @param {Object} options Custom ZoomControl settings
 
3806
         */
 
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');
 
3812
 
 
3813
        var range = $R(this.minZoomLevel, this.maxZoomLevel);
 
3814
        this.slider = new Control.Slider(this.handle, this.id + 'Track', {
 
3815
            axis: 'vertical',
 
3816
            values: range,
 
3817
            sliderValue: this.zoomLevel,
 
3818
            range: range,
 
3819
            onChange: this.changed.bind(this)
 
3820
        });
 
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) {
 
3824
                        Event.stop(e);
 
3825
                });
 
3826
        Event.observe($(this.id + 'ZoomOut'), 'mouseup', this.zoomButtonClicked.bind(this, 1));
 
3827
        Event.observe($(this.id + 'ZoomOut'), 'mousedown', function (e) {
 
3828
                Event.stop(e);
 
3829
        });
 
3830
    },
 
3831
 
 
3832
        /**
 
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).              
 
3835
         */
 
3836
    zoomButtonClicked: function (dir) {
 
3837
        this.slider.setValue(this.slider.value + dir);
 
3838
    },
 
3839
  
 
3840
        /**
 
3841
         * @description Adjusts the zoom-control slider
 
3842
         * @param {Integer} v The new zoom value.
 
3843
         */
 
3844
    changed: function (v) {
 
3845
        this.fire('change', v);
 
3846
    }
 
3847
});
 
 
b'\\ No newline at end of file'