I discovered JScience a couple months ago and it has been a huge help for my project, though I'm struggling with one thing.
I'm trying to create a PressureHead
(aka water column) unit that can be converted directly with Length
and indirectly with Pressure
given a VolumetricDensity
value.
To find pressure: Density × Gravity × Head = Pressure
Here is an example conversion from Wikipedia:
1 cmH2O (4°C) = 999.9720 kg/m3 × 9.80665 m/s2 × 1 cm = 98.063754138 Pa
1 cmH2O can be directly converted to 1 cm.
Let's say I know the pressure in Pa and want to find the pressure head in mH2O, which is the conversion I would be doing most often in my project. I'll also need to know the density of the fluid. The pressure and density are variable inputs. Gravity must also be known for the formula, but for my purposes it can be fixed to standard gravity.
To find pressure head: Pressure / (Density × Gravity) = Head
For sake of simplicity, I've just repurposed the values from the above example, multiplying pressure by 100 to get 1 mH2O instead of 1 cmH2O.
9806.3754138 Pa / (999.9720 kg/m3 × 9.80665 m/s2) = 1 mH2O
It looks like JScience may be flexible enough to allow such a unit, but I haven't seen any examples to help me create it. Worst case, I'll probably just settle for converting between them with util methods.
Some examples of the ideal usage I would like to see:
// Pressure to PressureHead
Amount<Pressure> pressure = Amount.valueOf(9806.3754138, PASCAL);
Amount<VolumetricDensity> density = Amount.valueOf(999.9720, KG_M3);
Amount<PressureHead> head = pressure.to(M_H2O, density);
System.out.println(pressure.doubleValue(M_H2O, density)); // 1.0
// PressureHead <-> Length
Amount<Length> length = head.to(METER);
System.out.println(head.doubleValue(METER)); // 1.0
head = length.to(M_H2O);
System.out.println(length.doubleValue(M_H2O)); // 1.0
// PressureHead to Pressure
pressure = head.to(PASCAL, density);
System.out.println(head.doubleValue(PASCAL, density)); // 9806.3754138
Converting between PressureHead
units is easy. I can define additional units like so:
Unit<PressureHead> FT_H2O = M_H2O.transform(FOOT.getConverterTo(METER));
For the ideal usage above, I would need to subclass Amount
and overload to()
and doubleValue()
. I suspect if there's a more proper way of doing the conversions (albeit not as pretty usage), it involves subclassing UnitConverter
and/or one of the DerivedUnit
-based classes.
Part of me wants to give up and just go the quick and easy (and ugly) route of util methods so I can move on to more important things, the other part of me wants to find a solution that makes me love JScience even more.
While I no longer need to do this kind of conversion at the moment, I'm revisiting this issue after converting most of the project (all except UI code) to Kotlin and finding reasons to love Kotlin. I made the switch because I saw it as a good opportunity to learn the language while the project was still in the early stages. As such, this doesn't exactly answer my past self who wanted a Java answer, but my current self is happy accepting this as the answer.
This solution utilizes Kotlin's extension functions.
To prevent the two directions clashing, they must use different function names or be defined in separate objects or packages.
object PressureToHead {
fun Amount<Pressure>.to(unit: Unit<PressureHead>,
fluidDensity: Amount<VolumetricDensity>): Amount<PressureHead> {
return Amount.valueOf(doubleValue(PASCAL)
/ (fluidDensity.doubleValue(KG_M3)
* SensorManager.GRAVITY_EARTH), M_H2O).to(unit)
}
fun Amount<Pressure>.doubleValue(unit: Unit<PressureHead>,
fluidDensity: Amount<VolumetricDensity>): Double {
return to(unit, fluidDensity).estimatedValue
}
}
object HeadToPressure {
fun Amount<PressureHead>.to(unit: Unit<Pressure>,
fluidDensity: Amount<VolumetricDensity>): Amount<Pressure> {
return Amount.valueOf(fluidDensity.doubleValue(KG_M3)
* SensorManager.GRAVITY_EARTH
* doubleValue(M_H2O), PASCAL).to(unit)
}
fun Amount<PressureHead>.doubleValue(unit: Unit<Pressure>,
fluidDensity: Amount<VolumetricDensity>): Double {
return to(unit, fluidDensity).estimatedValue
}
}
For converting between PressureHead
and Length
, I couldn't use the same function names because they shadowed the existing ones. The best solution seems to be to just give the functions a different name, which I'm okay with.
object HeadToLength {
fun Amount<PressureHead>.toLength(unit: Unit<Length>): Amount<Length> {
return Amount.valueOf(doubleValue(M_H2O), METER).to(unit)
}
fun Amount<PressureHead>.doubleValueLength(unit: Unit<Length>): Double {
return toLength(unit).estimatedValue
}
}
object LengthToHead {
fun Amount<Length>.toHead(unit: Unit<PressureHead>): Amount<PressureHead> {
return Amount.valueOf(doubleValue(METER), M_H2O).to(unit)
}
fun Amount<Length>.doubleValueHead(unit: Unit<PressureHead>): Double {
return toHead(unit).estimatedValue
}
}
Usage is exactly as I wanted aside from the small difference with the Length
conversions. This time written in Kotlin.
// Pressure to PressureHead
val density = Amount.valueOf(999.9720, KG_M3)
var pressure = Amount.valueOf(9806.3754138, PASCAL)
var head = pressure.to(M_H2O, density)
println(pressure.doubleValue(M_H2O, density)) // 1.0
// PressureHead to Pressure
pressure = head.to(PASCAL, density)
println(head.doubleValue(PASCAL, density)) // 9806.3754138
// PressureHead <-> Length
Amount<Length> length = head.toLength(METER)
println(head.doubleValueLength(METER)) // 1.0
head = length.toHead(M_H2O)
println(length.doubleValueHead(M_H2O)) // 1.0