Shopify / measured

Encapsulate measurements and their units in Ruby and Ruby on Rails.
MIT License
337 stars 28 forks source link

How to implement Measured::Area and Measured::Volume? #91

Open jocubeit opened 7 years ago

jocubeit commented 7 years ago

I added a custom implementation for Measured::Time, but having trouble working out how I might implement Area and Volume. I have a specific use-case for these for an app designed for the trades industry.

jocubeit commented 7 years ago

This is what I've come up with for Area so far, not sure how accurate the conversion figures are though as I used Google to convert from one unit to another:

Measured::Area = Measured.build do
  unit :mm2, value: ['0.01 cm2'], aliases: [:square_millimeter, :square_millimeters, :square_millimetre, :square_millimetres]
  unit :cm2, value: ['0.0001 m2'], aliases: [:square_centimeter, :square_centimeters, :square_centimetre, :square_centimetres]
  unit :m2,  aliases: [:square_meter, :square_meters, :square_metre, :square_metres]
  unit :km2, value: ['1000000 m2'],  aliases: [:square_kilometer, :square_kilometers, :square_kilometre, :square_kilometres]
  unit :mi2, value: ['2.58999 km2'],  aliases: [:square_mile, :square_miles]
  unit :yd2, value: ['0.836127970373 m2'],  aliases: [:square_yard, :square_yards]
  unit :ft2, value: ['0.092903 m2'],  aliases: [:square_foot, :square_feet]
  unit :in2, value: ['6.4516 cm2'], aliases: [:square_inch, :square_inches]
  unit :ha, value: ['10000 m2'], aliases: [:hectare, :hectares]
  unit :ac, value: ['4046.86 m2'], aliases: [:acre, :acres]
end

Any comments are most welcome.

jocubeit commented 7 years ago

And this is what I've come up with for Volume, again not sure how accurate it is:

Measured::Volume = Measured.build do
  unit :l, aliases: [:litre, :liter, :litres, :liters]
  unit :ml, value: ['0.001 l'], aliases: [:millilitre, :milliliter, :milliliter, :milliliters]
  unit :m3, value: ['1000 l'], aliases: [:cubic_meter, :cubic_meters, :cubic_metre, :cubic_metres]
  unit :ft3, value: ['0.0353147 l'], aliases: [:cubic_foot, :cubic_feet]
  unit :in3, value: ['0.0163871 l'], aliases: [:cubic_inch, :cubic_inches]
  unit :gal, value: ['4.54609 l'], aliases: [:imp_gal, :imperial_gallon, :imperial_gallons]
  unit :us_gal, value: ['3.78541 l'], aliases: [:us_gallon, :us_gallons, :us_liquid_gallon, :us_liquid_gallons]
  unit :qt, value: ['1.13652 l'], aliases: [:q, :imp_qt, :imperial_quart, :imperial_quarts]
  unit :us_qt, value: ['0.946353 l'], aliases: [:us_quart, :us_quarts, :us_fluid_quart, :us_fluid_quarts, :us_liquid_quart, :us_liquid_quarts]
  unit :pt, value: ['0.568261 l'], aliases: [:p, :imp_pt, :imperial_pint, :imperial_pints]
  unit :us_pt, value: ['0.473176 l'], aliases: [:us_pint, :us_pints, :us_liquid_pint, :us_liquid_pints]
  unit :oz, value: ['0.0284131 l'], aliases: [:fl_oz, :imp_fl_oz, :fluid_ounce, :imperial_fluid_ounce, :fluid_ounces, :imperial_fluid_ounces]
  unit :us_oz, value: ['0.0295735 l'], aliases: [:us_fl_oz, :us_fluid_ounce, :us_fluid_ounces]
end

Do you think these conversions are acceptable?

jocubeit commented 7 years ago

@thegedge, @kmcphillips I can see you two are active on this repo, any comments?

thegedge commented 7 years ago

Hey, @JurgenJocubeit, thanks for the issue and sorry I missed it (I'm watching the repo now 😸). These look good at first glance, although I haven't verified the numbers (I would do that in code review). If you'd like to push a PR (preferably one for area and one for volume), I'd be happy to review it.

My plan was to one day try to get something working for things like area and volume where you could multiply lengths together and get areas/volumes, but I haven't thought through such an implementation much (performance being the key concern). I do know that squared representations would come out like unit^2 with such a change, or maybe even squared unit,

Nevertheless, even if I could multiply 1m * 1m to get 1m^2, for example, there are area measurements that won't fit that pattern (e.g., acre, hectare), so there's a need for something like this. We can start with manually defining squared lengths, and once I get around to implementing such a change I'll figure out how to best deal with the construction of a unit system 😃

jocubeit commented 7 years ago

Thanks for your reply, I have forked and started two feature branches. Have the code and readme done, just need to finish the unit tests.

Although I agree that the ability to calculate area and volume from length inputs would be nice, I do feel it's a bit out of scope for the gem in it's current form. The gem tends to currently lean towards conversion. Of course, calculation would always be welcome, albeit quite a challenge! :-)

thegedge commented 7 years ago

Thanks for your reply, I have forked and started two feature branches. Have the code and readme done, just need to finish the unit tests.

Great! Just add me as a reviewer when you send out the PRs.

Although I agree that the ability to calculate area and volume from length inputs would be nice, I do feel it's a bit out of scope for the gem in it's current form.

Yeah, for now I don't have the time to invest in a prototype for this. But I'd love to be able to do something like this:

> distance = Measured::Weight.new("12", "m")
> time = Measured::Time.new("6", "s")
> distance / time
#<Measured::Speed: 2 #<Measured::Unit: m/s>>
jocubeit commented 7 years ago

Just wanted to let you know I haven't forgotten this, got waylaid on a few other things. Will come back to this tonight and finish writing those tests.

panbanda commented 4 years ago

Greetings! Is there a latest on this PR? I may have some time if I can get the latest.

saraid commented 2 years ago

Instead of coming up with new units, I'd suggest adding a new concept of "derived units". For example,

Measured::DerivedUnit.new(Measured::Unit.new(:m), :/, Measured::Unit.new(:s)) #=> #<Measured::DerivedUnit :"m/s">
Measured::DerivedUnit.new(Measured::Unit.new(:m), :*, Measured::Unit.new(:m)) #=> #<Measured::DerivedUnit :"m^2">

Velocity = Measured::DerivedUnit.new(Measured::Unit.new(:m), :/, Measured::Unit.new(:s))
Acceleration = Measured::DerivedUnit.new(Velocity, :/, Measured::Unit.new(:s))
Force = Measured::DerivedUnit.new(Measured::Unit.new(:kg), :*, Acceleration)

You may be able to go from there to defining Area as LengthLength and both Speed and Velocity as Length/Time. You might then want to go back and provide them as first-class citizens again by pre-deriving units, explicitly stating that `N=kgm/s/s` somehow.

I did something similar to this strategy here: https://github.com/saraid/units/blob/main/lib/units/derived_unit.rb but I wasn't considering performance, so I'm sure it wins no awards there. (This was some fairly hacky experimentation I did; I might take some time to clean it up and make it a real gem with tests and stuff at some point.)