1212 * Output coordinates are converted to degrees for consistent axis labeling.
1313 * The projection boundary is an ellipse.
1414 *
15+ * Supports optional eye FOV overlay from CSV data files containing
16+ * Mollweide-projected boundary coordinates (in radians).
17+ *
1518 * @module mollweide-viewer
1619 */
1720
@@ -23,6 +26,12 @@ const PI = Math.PI;
2326class MollweideViewer extends ProjectionViewer {
2427 constructor ( container ) {
2528 super ( container , 'mollweide' ) ;
29+
30+ // Eye FOV overlay state
31+ this . showEyeFOV = false ;
32+ this . eyeFOVLeft = null ;
33+ this . eyeFOVRight = null ;
34+ this . _eyeFOVLoading = false ;
2635 }
2736
2837 /**
@@ -107,15 +116,123 @@ class MollweideViewer extends ProjectionViewer {
107116 } ;
108117 }
109118
119+ // ========================================
120+ // Eye FOV overlay
121+ // ========================================
122+
123+ /**
124+ * Toggle eye FOV overlay visibility.
125+ * Loads CSV data lazily on first enable.
126+ * @param {boolean } show - Whether to show the eye FOV overlay
127+ */
128+ setShowEyeFOV ( show ) {
129+ this . showEyeFOV = show ;
130+ if ( show && ! this . eyeFOVLeft && ! this . _eyeFOVLoading ) {
131+ this . _loadEyeFOV ( ) . then ( ( ) => this . _render ( ) ) ;
132+ } else {
133+ this . _render ( ) ;
134+ }
135+ }
136+
137+ /**
138+ * Fetch and parse eye FOV boundary CSV files.
139+ * CSV files contain Mollweide-projected coordinates in radians (x1, y1).
140+ * Converts to degrees to match the viewer's coordinate system.
141+ */
142+ async _loadEyeFOV ( ) {
143+ this . _eyeFOVLoading = true ;
144+ try {
145+ const [ leftResp , rightResp ] = await Promise . all ( [
146+ fetch ( 'data/fov_left_Mo.csv' ) ,
147+ fetch ( 'data/fov_right_Mo.csv' )
148+ ] ) ;
149+
150+ if ( ! leftResp . ok || ! rightResp . ok ) {
151+ console . error ( 'Eye FOV: Failed to load CSV files' ) ;
152+ this . _eyeFOVLoading = false ;
153+ return ;
154+ }
155+
156+ const [ leftText , rightText ] = await Promise . all ( [ leftResp . text ( ) , rightResp . text ( ) ] ) ;
157+
158+ this . eyeFOVLeft = this . _parseEyeFOVCSV ( leftText ) ;
159+ this . eyeFOVRight = this . _parseEyeFOVCSV ( rightText ) ;
160+
161+ console . log (
162+ 'Eye FOV loaded:' ,
163+ this . eyeFOVLeft . length ,
164+ 'left pts,' ,
165+ this . eyeFOVRight . length ,
166+ 'right pts'
167+ ) ;
168+ } catch ( err ) {
169+ console . error ( 'Eye FOV: Load error:' , err . message ) ;
170+ }
171+ this . _eyeFOVLoading = false ;
172+ }
173+
110174 /**
111- * Draw Mollweide-specific decorations: the elliptical boundary.
175+ * Parse a CSV string of Mollweide coordinates (radians) into degree points.
176+ * @param {string } csvText - CSV with "x1","y1" header and radian values
177+ * @returns {Array<{x: number, y: number}> } Points in degrees (Mollweide map space)
178+ */
179+ _parseEyeFOVCSV ( csvText ) {
180+ const lines = csvText . trim ( ) . split ( '\n' ) ;
181+ const points = [ ] ;
182+ // Skip header row
183+ for ( let i = 1 ; i < lines . length ; i ++ ) {
184+ const line = lines [ i ] . trim ( ) ;
185+ if ( ! line ) continue ;
186+ const parts = line . split ( ',' ) ;
187+ if ( parts . length < 2 ) continue ;
188+ const xRad = parseFloat ( parts [ 0 ] ) ;
189+ const yRad = parseFloat ( parts [ 1 ] ) ;
190+ if ( isNaN ( xRad ) || isNaN ( yRad ) ) continue ;
191+ // Convert from Mollweide radians to degrees
192+ points . push ( {
193+ x : ( xRad * 180 ) / PI ,
194+ y : ( yRad * 180 ) / PI
195+ } ) ;
196+ }
197+ return points ;
198+ }
199+
200+ /**
201+ * Draw eye FOV boundary polygons on the projection.
202+ * @param {CanvasRenderingContext2D } ctx
203+ * @param {Function } mapToCanvas - Convert (mapX, mapY) => { cx, cy }
204+ */
205+ _drawEyeFOV ( ctx , mapToCanvas ) {
206+ ctx . strokeStyle = 'rgba(255, 255, 255, 0.8)' ;
207+ ctx . lineWidth = 2.5 ;
208+
209+ for ( const points of [ this . eyeFOVLeft , this . eyeFOVRight ] ) {
210+ if ( ! points || points . length < 3 ) continue ;
211+
212+ ctx . beginPath ( ) ;
213+ for ( let i = 0 ; i < points . length ; i ++ ) {
214+ const { cx, cy } = mapToCanvas ( points [ i ] . x , points [ i ] . y ) ;
215+ if ( i === 0 ) {
216+ ctx . moveTo ( cx , cy ) ;
217+ } else {
218+ ctx . lineTo ( cx , cy ) ;
219+ }
220+ }
221+ ctx . closePath ( ) ;
222+ ctx . stroke ( ) ;
223+ }
224+ }
225+
226+ // ========================================
227+ // Decorations
228+ // ========================================
229+
230+ /**
231+ * Draw Mollweide-specific decorations: elliptical boundary + optional eye FOV.
112232 */
113233 _drawDecorations ( ctx , mapToCanvas ) {
114234 // Draw the full-sphere ellipse outline
115- // The Mollweide boundary at full FOV is an ellipse:
116- // x = (2√2/π)·λ·cos(θ), y = √2·sin(θ)
117- // For the full boundary: λ = ±π, lat varies
118- ctx . strokeStyle = '#3d4a58' ;
235+ ctx . strokeStyle = 'rgba(255, 255, 255, 0.4)' ;
119236 ctx . lineWidth = 1 ;
120237 ctx . beginPath ( ) ;
121238
@@ -142,6 +259,11 @@ class MollweideViewer extends ProjectionViewer {
142259 }
143260 ctx . closePath ( ) ;
144261 ctx . stroke ( ) ;
262+
263+ // Draw eye FOV overlay if enabled and loaded
264+ if ( this . showEyeFOV && this . eyeFOVLeft && this . eyeFOVRight ) {
265+ this . _drawEyeFOV ( ctx , mapToCanvas ) ;
266+ }
145267 }
146268}
147269
0 commit comments