convert-units
Version:
Convert between quantities in different units
305 lines (257 loc) • 7.57 kB
JavaScript
var convert
, keys = require('lodash.keys')
, each = require('lodash.foreach')
, measures = {
length: require('./definitions/length')
, area: require('./definitions/area')
, mass: require('./definitions/mass')
, volume: require('./definitions/volume')
, each: require('./definitions/each')
, temperature: require('./definitions/temperature')
, time: require('./definitions/time')
, digital: require('./definitions/digital')
, partsPer: require('./definitions/partsPer')
, speed: require('./definitions/speed')
, pace: require('./definitions/pace')
, pressure: require('./definitions/pressure')
, current: require('./definitions/current')
, voltage: require('./definitions/voltage')
, power: require('./definitions/power')
, reactivePower: require('./definitions/reactivePower')
, apparentPower: require('./definitions/apparentPower')
, energy: require('./definitions/energy')
, reactiveEnergy: require('./definitions/reactiveEnergy')
, volumeFlowRate: require('./definitions/volumeFlowRate')
, illuminance: require('./definitions/illuminance')
, frequency: require('./definitions/frequency')
, angle : require('./definitions/angle')
}
, Converter;
Converter = function (numerator, denominator) {
if(denominator)
this.val = numerator / denominator;
else
this.val = numerator;
};
/**
* Lets the converter know the source unit abbreviation
*/
Converter.prototype.from = function (from) {
if(this.destination)
throw new Error('.from must be called before .to');
this.origin = this.getUnit(from);
if(!this.origin) {
this.throwUnsupportedUnitError(from);
}
return this;
};
/**
* Converts the unit and returns the value
*/
Converter.prototype.to = function (to) {
if(!this.origin)
throw new Error('.to must be called after .from');
this.destination = this.getUnit(to);
var result
, transform;
if(!this.destination) {
this.throwUnsupportedUnitError(to);
}
// Don't change the value if origin and destination are the same
if (this.origin.abbr === this.destination.abbr) {
return this.val;
}
// You can't go from liquid to mass, for example
if(this.destination.measure != this.origin.measure) {
throw new Error('Cannot convert incompatible measures of '
+ this.destination.measure + ' and ' + this.origin.measure);
}
/**
* Convert from the source value to its anchor inside the system
*/
result = this.val * this.origin.unit.to_anchor;
/**
* For some changes it's a simple shift (C to K)
* So we'll add it when convering into the unit (later)
* and subtract it when converting from the unit
*/
if (this.origin.unit.anchor_shift) {
result -= this.origin.unit.anchor_shift
}
/**
* Convert from one system to another through the anchor ratio. Some conversions
* aren't ratio based or require more than a simple shift. We can provide a custom
* transform here to provide the direct result
*/
if(this.origin.system != this.destination.system) {
transform = measures[this.origin.measure]._anchors[this.origin.system].transform;
if (typeof transform === 'function') {
result = transform(result)
}
else {
result *= measures[this.origin.measure]._anchors[this.origin.system].ratio;
}
}
/**
* This shift has to be done after the system conversion business
*/
if (this.destination.unit.anchor_shift) {
result += this.destination.unit.anchor_shift;
}
/**
* Convert to another unit inside the destination system
*/
return result / this.destination.unit.to_anchor;
};
/**
* Converts the unit to the best available unit.
*/
Converter.prototype.toBest = function(options) {
if(!this.origin)
throw new Error('.toBest must be called after .from');
var options = Object.assign({
exclude: [],
cutOffNumber: 1,
}, options)
var best;
/**
Looks through every possibility for the 'best' available unit.
i.e. Where the value has the fewest numbers before the decimal point,
but is still higher than 1.
*/
each(this.possibilities(), function(possibility) {
var unit = this.describe(possibility);
var isIncluded = options.exclude.indexOf(possibility) === -1;
if (isIncluded && unit.system === this.origin.system) {
var result = this.to(possibility);
if (!best || (result >= options.cutOffNumber && result < best.val)) {
best = {
val: result,
unit: possibility,
singular: unit.singular,
plural: unit.plural
};
}
}
}.bind(this));
return best;
}
/**
* Finds the unit
*/
Converter.prototype.getUnit = function (abbr) {
var found;
each(measures, function (systems, measure) {
each(systems, function (units, system) {
if(system == '_anchors')
return false;
each(units, function (unit, testAbbr) {
if(testAbbr == abbr) {
found = {
abbr: abbr
, measure: measure
, system: system
, unit: unit
};
return false;
}
});
if(found)
return false;
});
if(found)
return false;
});
return found;
};
var describe = function(resp) {
return {
abbr: resp.abbr
, measure: resp.measure
, system: resp.system
, singular: resp.unit.name.singular
, plural: resp.unit.name.plural
};
}
/**
* An alias for getUnit
*/
Converter.prototype.describe = function (abbr) {
var resp = Converter.prototype.getUnit(abbr);
var desc = null;
try {
desc = describe(resp);
} catch(err) {
this.throwUnsupportedUnitError(abbr);
}
return desc;
};
/**
* Detailed list of all supported units
*/
Converter.prototype.list = function (measure) {
var list = [];
each(measures, function (systems, testMeasure) {
if(measure && measure !== testMeasure)
return;
each(systems, function (units, system) {
if(system == '_anchors')
return false;
each(units, function (unit, abbr) {
list = list.concat(describe({
abbr: abbr,
measure: testMeasure
, system: system
, unit: unit
}));
});
});
});
return list;
};
Converter.prototype.throwUnsupportedUnitError = function (what) {
var validUnits = [];
each(measures, function (systems, measure) {
each(systems, function (units, system) {
if(system == '_anchors')
return false;
validUnits = validUnits.concat(keys(units));
});
});
throw new Error('Unsupported unit ' + what + ', use one of: ' + validUnits.join(', '));
}
/**
* Returns the abbreviated measures that the value can be
* converted to.
*/
Converter.prototype.possibilities = function (measure) {
var possibilities = [];
if(!this.origin && !measure) {
each(keys(measures), function (measure){
each(measures[measure], function (units, system) {
if(system == '_anchors')
return false;
possibilities = possibilities.concat(keys(units));
});
});
} else {
measure = measure || this.origin.measure;
each(measures[measure], function (units, system) {
if(system == '_anchors')
return false;
possibilities = possibilities.concat(keys(units));
});
}
return possibilities;
};
/**
* Returns the abbreviated measures that the value can be
* converted to.
*/
Converter.prototype.measures = function () {
return keys(measures);
};
convert = function (value) {
return new Converter(value);
};
module.exports = convert;