|
23 | 23 | <input id="fits-url" type="url" placeholder="https://data.nasa.gov/sample.fits" aria-label="FITS file URL" /> |
24 | 24 | <button type="button" id="load-url">Load URL</button> |
25 | 25 | </div> |
| 26 | + <div class="comparison"> |
| 27 | + <button type="button" id="open-comparison">Compare</button> |
| 28 | + </div> |
26 | 29 | </div> |
27 | 30 | <div class="samples"> |
28 | 31 | <span class="samples-label">Try a sample dataset:</span> |
|
58 | 61 | </p> |
59 | 62 | <p class="notice hidden" id="aladin-error" role="alert"></p> |
60 | 63 | </aside> |
61 | | - <script> |
62 | | - const { A } = window; |
63 | | - if (!A) { |
64 | | - console.error('Aladin Lite library failed to load.'); |
65 | | - } |
66 | | - |
67 | | - const statusMessage = document.getElementById('status-message'); |
68 | | - const errorBox = document.getElementById('aladin-error'); |
69 | | - const fileInput = document.getElementById('fits-file'); |
70 | | - const urlInput = document.getElementById('fits-url'); |
71 | | - const loadUrlButton = document.getElementById('load-url'); |
72 | | - const sampleButtons = Array.from(document.querySelectorAll('[data-fits-url]')); |
73 | | - |
74 | | - let aladinInstance = null; |
75 | | - let currentRequestId = 0; |
76 | | - |
77 | | - const DEFAULT_SAMPLE = sampleButtons.length |
78 | | - ? { |
79 | | - url: sampleButtons[0].dataset.fitsUrl, |
80 | | - label: sampleButtons[0].dataset.label, |
81 | | - colormap: sampleButtons[0].dataset.colormap |
82 | | - } |
83 | | - : null; |
84 | | - |
85 | | - function showError(message) { |
86 | | - errorBox.textContent = message; |
87 | | - errorBox.classList.remove('hidden'); |
88 | | - } |
89 | | - |
90 | | - function clearError() { |
91 | | - errorBox.textContent = ''; |
92 | | - errorBox.classList.add('hidden'); |
93 | | - } |
94 | | - |
95 | | - function updateStatus(label, fov) { |
96 | | - const parts = []; |
97 | | - if (label) { |
98 | | - parts.push(`Source: ${label}`); |
99 | | - } |
100 | | - if (Number.isFinite(fov)) { |
101 | | - const span = Math.max(0.01, fov * 2); |
102 | | - parts.push(`FoV ≈ ${span.toFixed(2)}°`); |
103 | | - } |
104 | | - statusMessage.textContent = parts.join(' · ') || 'Ready to load a FITS image.'; |
105 | | - } |
106 | | - |
107 | | - function focusOnImage(ra, dec, fov) { |
108 | | - if (Number.isFinite(ra) && Number.isFinite(dec)) { |
109 | | - aladinInstance.gotoRaDec(ra, dec); |
110 | | - } |
111 | | - if (Number.isFinite(fov)) { |
112 | | - const clamped = Math.min(60, Math.max(0.01, fov * 2)); |
113 | | - aladinInstance.setFoV(clamped); |
114 | | - } |
115 | | - } |
116 | | - |
117 | | - function configureImage(image, colormap) { |
118 | | - if (!image) { |
119 | | - return; |
120 | | - } |
121 | | - try { |
122 | | - image.setColormap(colormap || 'magma', { stretch: 'sqrt' }); |
123 | | - } catch (error) { |
124 | | - console.warn('Unable to update the FITS colormap', error); |
125 | | - } |
126 | | - } |
127 | | - |
128 | | - function deriveLabel(source) { |
129 | | - if (typeof source === 'string') { |
130 | | - try { |
131 | | - const url = new URL(source); |
132 | | - return url.hostname; |
133 | | - } catch { |
134 | | - return source; |
135 | | - } |
136 | | - } |
137 | | - if (source && typeof source.name === 'string') { |
138 | | - return `Local file: ${source.name}`; |
139 | | - } |
140 | | - return 'Custom FITS'; |
141 | | - } |
142 | | - |
143 | | - function loadFits(source, options = {}) { |
144 | | - if (!aladinInstance || !source) { |
145 | | - return; |
146 | | - } |
147 | | - |
148 | | - const requestId = ++currentRequestId; |
149 | | - const label = options.label || deriveLabel(source); |
150 | | - |
151 | | - clearError(); |
152 | | - statusMessage.textContent = `Loading ${label}…`; |
153 | | - const loadTimeout = window.setTimeout(() => { |
154 | | - if (requestId !== currentRequestId) { |
155 | | - return; |
156 | | - } |
157 | | - console.warn(`FITS load timed out: ${label}`); |
158 | | - showError('The FITS file is taking longer than expected to respond. Please try again or use a different source.'); |
159 | | - }, options.timeout ?? 20000); |
160 | | - |
161 | | - let finished = false; |
162 | | - |
163 | | - const finish = () => { |
164 | | - if (finished) { |
165 | | - return; |
166 | | - } |
167 | | - finished = true; |
168 | | - window.clearTimeout(loadTimeout); |
169 | | - if (typeof options.onCleanup === 'function') { |
170 | | - try { |
171 | | - options.onCleanup(); |
172 | | - } catch (cleanupError) { |
173 | | - console.warn('Cleanup callback failed', cleanupError); |
174 | | - } |
175 | | - } |
176 | | - }; |
177 | | - |
178 | | - const handleSuccess = (ra, dec, fov, image) => { |
179 | | - if (requestId !== currentRequestId) { |
180 | | - return; |
181 | | - } |
182 | | - finish(); |
183 | | - configureImage(image, options.colormap); |
184 | | - focusOnImage(ra, dec, fov); |
185 | | - updateStatus(label, fov); |
186 | | - }; |
187 | | - |
188 | | - const handleError = (error) => { |
189 | | - if (requestId !== currentRequestId) { |
190 | | - return; |
191 | | - } |
192 | | - finish(); |
193 | | - console.error('Failed to load FITS data', error); |
194 | | - showError('Unable to load the FITS data. Please verify the file or URL and try again.'); |
195 | | - }; |
196 | | - |
197 | | - try { |
198 | | - const result = aladinInstance.displayFITS(source, options.params || {}, handleSuccess, handleError); |
199 | | - if (result && typeof result.then === 'function') { |
200 | | - result.catch(handleError).finally(finish); |
201 | | - } |
202 | | - } catch (error) { |
203 | | - handleError(error); |
204 | | - } |
205 | | - } |
206 | | - |
207 | | - function handleFileSelection(event) { |
208 | | - const [file] = event.target.files || []; |
209 | | - if (!file) { |
210 | | - return; |
211 | | - } |
212 | | - const objectUrl = URL.createObjectURL(file); |
213 | | - loadFits(objectUrl, { |
214 | | - label: `Local file: ${file.name}`, |
215 | | - onCleanup: () => URL.revokeObjectURL(objectUrl) |
216 | | - }); |
217 | | - event.target.value = ''; |
218 | | - } |
219 | | - |
220 | | - function handleUrlLoad() { |
221 | | - const url = urlInput.value.trim(); |
222 | | - if (!url) { |
223 | | - showError('Please enter a FITS file URL.'); |
224 | | - return; |
225 | | - } |
226 | | - try { |
227 | | - new URL(url); |
228 | | - } catch { |
229 | | - showError('The URL appears to be invalid. Please double-check and try again.'); |
230 | | - return; |
231 | | - } |
232 | | - loadFits(url, { label: url }); |
233 | | - } |
234 | | - |
235 | | - function initialiseSampleButtons() { |
236 | | - sampleButtons.forEach((button) => { |
237 | | - button.addEventListener('click', () => { |
238 | | - const { fitsUrl, label, colormap } = button.dataset; |
239 | | - loadFits(fitsUrl, { label, colormap }); |
240 | | - }); |
241 | | - }); |
242 | | - } |
243 | | - |
244 | | - async function initialiseViewer() { |
245 | | - if (!A) { |
246 | | - showError('Aladin Lite could not be loaded. Please check your connection and refresh the page.'); |
247 | | - return; |
248 | | - } |
249 | | - try { |
250 | | - A.init.then(() => { |
251 | | - aladinInstance = A.aladin('#aladin-lite-div', { |
252 | | - cooFrame: 'icrs', |
253 | | - projection: 'AIT', |
254 | | - showCooGrid: false, |
255 | | - fullScreen: true, |
256 | | - fov: 5, |
257 | | - target: 'M 31' |
258 | | - }); |
259 | | - |
260 | | - var marker1 = A.marker(0, 0, {popupTitle: "Test", popupDesc: "TEST"}); |
261 | | - var markerLayer = A.catalog(); |
262 | | - aladinInstance.addCatalog(markerLayer); |
263 | | - markerLayer.addSources([marker1]); |
264 | | - }); |
265 | | - |
266 | | - initialiseSampleButtons(); |
267 | | - if (DEFAULT_SAMPLE) { |
268 | | - loadFits(DEFAULT_SAMPLE.url, DEFAULT_SAMPLE); |
269 | | - } else { |
270 | | - updateStatus(null); |
271 | | - } |
272 | | - } catch (error) { |
273 | | - console.error('Aladin Lite failed to initialise', error); |
274 | | - showError('The Aladin Lite viewer could not be initialised. Please refresh the page.'); |
275 | | - } |
276 | | - } |
277 | | - |
278 | | - fileInput.addEventListener('change', handleFileSelection); |
279 | | - fileInput.addEventListener('click', () => { |
280 | | - clearError(); |
281 | | - }); |
282 | | - |
283 | | - loadUrlButton.addEventListener('click', handleUrlLoad); |
284 | | - urlInput.addEventListener('keydown', (event) => { |
285 | | - if (event.key === 'Enter') { |
286 | | - event.preventDefault(); |
287 | | - handleUrlLoad(); |
288 | | - } |
289 | | - }); |
290 | | - |
291 | | - initialiseViewer(); |
292 | | - </script> |
| 64 | + <script src="static/front_end.js"></script> |
293 | 65 | </body> |
294 | 66 | </html> |
0 commit comments