@@ -12,8 +12,8 @@ import {WAVELENGTH_UNITS} from 'firefly/visualize/VisUtil';
12
12
* @returns {boolean }
13
13
*/
14
14
export function canUnitConv ( { from, to} ) {
15
- const fromKey = normalizeUnit ( from ) ;
16
- const toKey = normalizeUnit ( to ) ;
15
+ const { unit : fromKey } = normalizeUnit ( from ) ;
16
+ const { unit : toKey } = normalizeUnit ( to ) ;
17
17
return ! ! ( fromKey && toKey && UnitXref ?. [ fromKey ] ?. [ toKey ] ) ;
18
18
}
19
19
@@ -28,9 +28,14 @@ export function canUnitConv({from, to}) {
28
28
* @returns {string }
29
29
*/
30
30
export function getUnitConvExpr ( { cname, from, to, alias, args= [ ] } ) {
31
- const fromKey = normalizeUnit ( from ) ;
32
- const toKey = normalizeUnit ( to ) ;
33
- const formula = UnitXref ?. [ fromKey ] ?. [ toKey ] ?? '' ;
31
+ const { unit : fromKey , factor : fromFactor } = normalizeUnit ( from ) ;
32
+ const { unit : toKey , factor : toFactor } = normalizeUnit ( to ) ;
33
+
34
+ let formula = UnitXref ?. [ fromKey ] ?. [ toKey ] ?? '' ;
35
+ if ( fromFactor !== toFactor ) {
36
+ if ( fromFactor ) formula = `${ formula } * ${ fromFactor } ` ;
37
+ if ( toFactor ) formula = `${ formula } / ${ toFactor } ` ; //todo: do we need parantheses here?
38
+ }
34
39
35
40
let colOrExpr = cname ;
36
41
if ( formula ) {
@@ -50,14 +55,19 @@ export function getUnitInfo(unit, cname) {
50
55
let options = [ ] ;
51
56
let label = '' ;
52
57
53
- const unitKey = normalizeUnit ( unit ) ;
58
+ const { unit : unitKey , factor } = normalizeUnit ( unit ) ;
54
59
if ( unitKey ) {
55
60
const measurementKey = UnitMetadata [ unitKey ] ?. type ;
61
+ let unitLabel = UnitMetadata [ unitKey ] ?. label || unitKey ;
56
62
options = Object . entries ( UnitMetadata )
57
63
. filter ( ( [ , meta ] ) => meta ?. type === measurementKey ) // can only convert units of the same measurement type
58
64
. map ( ( [ key , meta ] ) => ( { value : key , label : meta . label || key } ) ) ;
59
65
60
- const unitLabel = UnitMetadata [ unitKey ] ?. label || unitKey ;
66
+ if ( factor ) { // add the original unit with its factor as the label and as the first option in conversion dropdown
67
+ unitLabel = `${ factor } ${ unitLabel } ` ;
68
+ options = [ { value : unit , label : unitLabel } , ...options ] ;
69
+ }
70
+
61
71
const formattedLabel = Measurement [ measurementKey ] ?. axisLabel ;
62
72
if ( formattedLabel ) label = sprintf ( formattedLabel , unitLabel ) ;
63
73
}
@@ -293,6 +303,7 @@ const UnitMetadata = {
293
303
'W/m^2/Hz' : {
294
304
type : Measurement . F_NU . key ,
295
305
label : 'W/m²/Hz' ,
306
+ // aliases are generated at runtime for this and all the flux units below
296
307
} ,
297
308
'erg/s/cm^2/Hz' : {
298
309
type : Measurement . F_NU . key ,
@@ -326,28 +337,42 @@ const UnitMetadata = {
326
337
} ;
327
338
328
339
340
+ // created by using "C.4 The VOUnits grammar" in https://ivoa.net/documents/VOUnits/20231215/REC-VOUnits-1.1.pdf
341
+ const VoUnitRegex = / ^ ( (?: 0 \. \d + | [ 1 - 9 ] \d * (?: \. \d + ) ? ) (?: [ e E ] [ + - ] ? [ 0 - 9 ] + ) ? ) ( .* ) $ / ;
342
+ const splitFactorUnit = ( u ) => {
343
+ const match = u . match ( VoUnitRegex ) ;
344
+ const factor = match ?. [ 1 ] ?? '' ;
345
+ const unit = ( match ?. [ 2 ] ?? u ) . trim ( ) ; // fallback to the whole string
346
+ return { factor, unit} ;
347
+ } ;
348
+
349
+
329
350
/**
330
- * Maps any unit representation (value/label/alias) back to a key in UnitXref (and UnitMetadata).
351
+ * Maps any unit representation (value/label/alias) back to a key in UnitXref (and UnitMetadata). Also returns the
352
+ * scalar factor if present.
331
353
* @param u {string} - the unit to normalize
332
- * @return {UnitKey|null } - the key in UnitXref if found, otherwise null
354
+ * @return {{unit: UnitKey|null, factor: string} } - the key in UnitXref if found, otherwise null and the scalar factor.
333
355
*/
334
356
function normalizeUnit ( u ) {
335
- if ( ! u ) return null ;
336
- // u is already a key in UnitXref
337
- if ( UnitXref [ u ] ) return u ;
357
+ if ( ! u ) return { unit : null , factor : '' } ;
358
+ const { factor, unit} = splitFactorUnit ( u ) ;
359
+
360
+ // unit is already a key in UnitXref
361
+ if ( UnitXref [ u ] ) return { unit, factor} ;
338
362
339
363
for ( const [ key , meta ] of Object . entries ( UnitMetadata ) ) {
340
- // u is an alias of a key in UnitXref
364
+ // unit is an alias of a key in UnitXref
341
365
const aliases = meta ?. aliases ?? [ ] ;
342
366
aliases . push ( ...getFluxAliases ( key ) ) ;
343
- if ( aliases . some ( ( alias ) => alias === u ) ) return key ;
367
+ if ( aliases . some ( ( alias ) => alias === unit ) ) return { unit : key , factor } ;
344
368
345
- // u is a label of a key in UnitXref
346
- if ( meta ?. label === u ) return key ;
369
+ // unit is a label of a key in UnitXref
370
+ if ( meta ?. label === u ) return { unit : key , factor } ;
347
371
}
348
- return null ;
372
+ return { unit : null , factor } ;
349
373
}
350
374
375
+
351
376
/* Get all possible representations (key, label, aliases) for a given unitKey */
352
377
function getAllRepresentations ( unitKey , includeLabel = true ) {
353
378
const meta = UnitMetadata [ unitKey ] ;
@@ -408,8 +433,6 @@ const getFluxAliases = (unitKey) => {
408
433
. map ( ( [ unit , power ] ) => power === 1 ? unit : `${ unit } **${ power } ` )
409
434
. join ( '.' ) ; // e.g. erg.s**-1.cm**-2.A**-1
410
435
411
- // TODO: also support numerical scale-factor in section 2.10 in https://ivoa.net/documents/VOUnits/20231215/REC-VOUnits-1.1.pdf
412
- // possibly alias function can return a boolean to do such matching
413
436
aliases . push ( fluxUnit , asteriskPowerExpr , invisiblePowerExpr , multiplicationExpr ) ;
414
437
}
415
438
return Array . from ( new Set ( aliases ) ) ; // remove duplicates
0 commit comments