package Data::Dimensions::Map;
# hold information about units provided by the module
use strict;
use vars qw(%SI_base %SI_units %units %basic %prefixes);
# basic SI units only need prefix scaling...
%SI_base = (
m => {m =>1 },
kg => {kg =>1},
s => {s =>1 },
A => {A =>1 },
K => {K =>1 },
cd => {cd =>1},
mol=> {mol=>1},
rad=> {rad=>1},
sr => {sr =>1},
Hz => {s => -1},
N => {kg => 1, m => 1, s => -2},
Pa => {kg => 1, m => -1,s => -2},
J => {kg => 1, m => 2, s => -2},
W => {kg => 1, m => 2, s => -3},
coul => {A => 1, s=> 1},
V => {kg =>1, m=> 2, s=> -3, A => -1},
ohm => {kg =>1, m=> 2, s=> -3, A => -2},
S => {kg =>-1,m=>-2, s=> 3, A => 2},
F => {kg =>-1,m=>-2, s=> 4, A => 2},
Wb => {kg =>1, m=> 2, s=> -2, A => -1},
H => {kg =>1, m=> 2, s=> -2, A => -2},
T => {kg =>1, m=> 1, s=> -2, A => -1},
lm => {cd =>1, m=>-2},
Bq => {s => -1},
Gy => {m => 2, s => -2},
);
%SI_units = (
%SI_base,
meter=> $SI_base{m},
kilo=> $SI_base{kg},
kilogram=> $SI_base{kg},
kilogramme=> $SI_base{kg},
sec=> $SI_base{s},
second=> $SI_base{s},
amp=> $SI_base{A},
ampere=> $SI_base{A},
kelvin=> $SI_base{K},
candela=> $SI_base{cd},
mole=> $SI_base{mol},
radian => $SI_base{rad},
steradian=> $SI_base{sr},
sterad=> $SI_base{sr},
hertz=> $SI_base{Hz},
newton=> $SI_base{N},
pascal=> $SI_base{Pa},
joule=> $SI_base{J},
watt=> $SI_base{W},
coulomb=> $SI_base{coul},
volt=> $SI_base{V},
seimens=> $SI_base{S},
farad=> $SI_base{F},
weber=> $SI_base{Wb},
henry=> $SI_base{H},
tesla=> $SI_base{T},
lumen=> $SI_base{lm},
becquerel=> $SI_base{Bq},
gray=> $SI_base{Gy},
);
# This should be invoked last... We know that SI units cannot change
# the scaling of the base value, so we can ignore that here
sub parse_SI {
my ($hr, $scale) = @_;
my %temp;
foreach my $unit (keys %$hr) {
if (exists $SI_units{$unit}) {
foreach (keys %{$SI_units{$unit}}) {
$temp{$_} += $SI_units{$unit}->{$_} * $hr->{$unit};
}
}
else {
$temp{$unit} += $hr->{$unit};
}
}
return (\%temp, $scale);
}
%prefixes = (
semi=> 0.5,
demi=> 0.5,
yotta=> 1e24,
zetta=> 1e21,
exa=> 1e18,
peta=> 1e15,
tera=> 1e12,
giga=> 1e9,
mega=> 1e6,
kilo=> 1e3,
hecto=> 1e2,
deka=> 1e1,
deci=> 1e-1,
centi=> 1e-2,
milli=> 1e-3,
micro=> 1e-6,
nano=> 1e-9,
pico=> 1e-12,
femto=> 1e-15,
atto=> 1e-18,
zopto=> 1e-21,
yocto=> 1e-24,
Y=> 1e24,
Z=> 1e21,
E=> 1e18,
P=> 1e15,
T=> 1e12,
G=> 1e9,
M=> 1e6,
k=> 1e3,
h=> 1e2,
da=> 1e1,
d=> 1e-1,
c=> 1e-2,
m=> 1e-3,
u=> 1e-6,
n=> 1e-9,
p=> 1e-12,
f=> 1e-15,
a=> 1e-18,
z=> 1e-21,
y=> 1e-24,
);
sub parse_prefix {
my ($hr, $scale) = @_;
my %temp;
my $rg = join('|', keys %prefixes);
my $rx = qr/^($rg)\-(\w+)$/;
foreach (keys %$hr) {
if ($_ =~ $rx) {
$temp{$2} = $hr->{$_};
$scale *= $prefixes{$1}**($hr->{$_});
}
else {
$temp{$_} = $hr->{$_};
}
}
return (\%temp, $scale);
}
# $units{ unit } = [scale, {basic hash}];
%units = (
turn => [ 2 * 3.14159, {rad=>1}],
revolution => [ 2 * 3.14159, {rad=>1}],
degree => [ 2*3.14159 / 360, {rad=>1}],
deg => [ 2*3.14159 / 360, {rad=>1}],
arcdeg => [ 2*3.14159 / 360, {rad=>1}],
arcmin => [ 2*3.14159 / (360*60), {rad=>1}],
arcsec => [ 2*3.14159 / (360*60*60), {rad=>1}],
minute => [ 60, {s=>1}],
min => [ 60, {s=>1}],
hour=> [ 60*60, {s=>1}],
hr=> [ 60*60, {s=>1}],
day => [ 24*60*60, {s=>1}],
week=> [ 7*24*60*60, {s=>1}],
year=> [ 365*24*60*60, {s=>1}], # non-leap
yr=> [ 365*24*60*60, {s=>1}],
gram=> [ 1e-3, {kg=>1}],
gm=> [1e-3 , {kg=>1}],
tonne=> [1e3 , {kg=>1}],
# avoirdupois
lb=> [.45359237 , {kg=>1}],
pound=> [ .45359237, {kg=>1}],
ounce=> [ .45359237/16, {kg=>1}],
oz=> [ .45359237/16, {kg=>1}],
micron => [ 1e-6, {m=>1}],
angstrom=> [ 1e-10, {m=>1}],
# Imperial
inch=> [2.54 / 100 , {m=>1}],
in => [ 2.54 / 100, {m=>1}],
foot=> [ 12*2.54 / 100, {m=>1}],
feet=> [12*2.54 / 100 , {m=>1}],
ft=> [ 12*2.54 / 100, {m=>1}],
yard=> [3*12*2.54 / 100, {m=>1}],
yd=> [ 3*12*2.54 / 100, {m=>1}],
mile=> [ 5280*12*2.54 / 100, {m=>1}],
nmile=> [ 1852, {m=>1}],
acre=> [4840 * (3*12*2.54 / 100)**2 , {m=>2}],
hectare => [100*100, {m => 2}],
cc=> [(1/100)**3 , {m=>3}],
liter=> [ 1000*((1/100)**3), {m=>3}],
ml => [ (1/100)**3, {m=>3}],
# US Liquid
gallon => [231 * (2.54 / 100)**3 , {m=>3}],
gal => [ 231 * (2.54 / 100)**3, {m=>3}],
quart => [ 231 * (2.54 / 100)**3 / 4, {m=>3}],
pint => [ 231 * (2.54 / 100)**3 / 8, {m=>3}],
# UK Liquid
brgallon => [277.420 * (2.54 / 100)**3 , {m=>3}],
brquart=> [ 277.420 * (2.54 / 100)**3 / 4, {m=>3}],
brpint => [ 277.420 * (2.54 / 100)**3 / 8, {m=>3}],
cal=> [ 4.1868, {joule=>1}],
mho=> [ 1, {ohm=>-1}],
baud => [ 1, {bit=>1, s=>-1}],
byte => [ 8 ,{bit=>1}],
block=> [ 512*8, {bit=>1}],
barn => [1e-28 , {m=>2}],
amu => [ 1.66044e-27, {kg=>1}],
electronvolt => [ 1.6021764e-19, {joule=>1}],
erg => [(1/100)**2 * (1/1000) , {m=>2, kg=>1, s=>-2}],
fermi=> [1e-15 , {m=>1}],
lightyear=> [ 365.25 * 299_792_458 * 60 * 60 * 24, {m=>1}],
parsec => [ 3.24 * 365.25 * 299_792_458 * 60 * 60 * 24, {m=>1}],
point=> [(1/72) * 2.54 / 100 , {m=>1}],
celsius => [1 , {K=>1}], # although no zero point changes
centrigrade=> [1 , {K=>1}],
siderealyear=> [ 365.256360417* 24*60*60, {s=>1}],
percent => [ 0.01, {}],
# test me baby!
__HONK_IF_YOU_PERL => [10, {m=>1, s=>-1}],
);
# This is the general scaling and convert to base monster
sub parse_other {
my ($hr, $scale) = @_;
my %temp;
foreach my $unit (keys %$hr) {
if (exists $units{$unit}) {
my $factor = $units{$unit}->[0];
my $exp = $hr->{$unit};
foreach (keys %{$units{$unit}->[1]}) {
$temp{$_} += $units{$unit}->[1]->{$_} * $exp;
}
$scale *= $factor ** $exp;
}
else {
$temp{$unit} = $hr->{$unit};
}
}
return (\%temp, $scale);
}
1;
__END__
=head1 Data::Units::Map
This package implements the mapping between base and extended units,
including the numerical conversions where appropriate.
=cut