-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #12 from WildernessLabs/develop
RC2
- Loading branch information
Showing
7 changed files
with
230 additions
and
13 deletions.
There are no files selected for viewing
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,82 @@ | ||
<img src="Design/banner.jpg" style="margin-bottom:10px" /> | ||
|
||
# Meadow.Units | ||
|
||
This repo contains a strong unitization into the entire stack of Meadow. No more ambiguous `float` returns for temperature, pressure, distance, voltage, or other units. Instead sensors return strongly-typed units such as `Temperature` and `Voltage`, with properties that do automatic conversions to the various representations, such as `Celsius` or `Fahrenheit`. | ||
|
||
## Contents | ||
* [Using unitization](#using-unitization) | ||
* [Nullable Everywhere](#nullable-everywhere) | ||
* [Unit types](#unit-types) | ||
* [Unit Conversions](#unit-conversions) | ||
|
||
## Using unitization | ||
|
||
Consider the following `AnalogTemperature` sensor sample: | ||
|
||
```csharp | ||
analogTemperature.TemperatureUpdated += (object sender, IChangeResult<Temperature> result) => | ||
{ | ||
Console.WriteLine($"Temp Changed, temp: {result.New.Celsius:N2}C, old: {result.Old?.Celsius:N2}C"); | ||
}; | ||
``` | ||
|
||
When the `TemperatureUpdated` event is raised, it passes an `IChangeResult<Temperature>` parameter. That result has a `New` and `Old` property of type `Temperature`, whose value can be accessed via a direct conversion to `Celsius`, `Fahrenheit`, or `Kelvin` on properties of the same name. | ||
|
||
## Nullable Everywhere | ||
|
||
Not also that Meadow fully embraces C# 8’s nullable features, so the result’s `Old` property is nullable, and in fact, on the first reading, it will be `null` because there isn’t a previous reading to populate it with. | ||
|
||
## Unit types | ||
|
||
We've added a number of unit types that describe a measure of something, including: | ||
|
||
* `AbsoluteHumidity` | ||
* `Acceleration` | ||
* `Acceleration3D` | ||
* `Angle` | ||
* `AngularAcceleration` | ||
* `AngularAcceleration3D` | ||
* `AngularVelocity` | ||
* `Azimuth` | ||
* `Concentration` | ||
* `Current` | ||
* `Density` | ||
* `Energy` | ||
* `Frequency` | ||
* `Illuminance` | ||
* `Length` | ||
* `MagneticField` | ||
* `MagneticField3D` | ||
* `Mass` | ||
* `Power` | ||
* `Pressure` | ||
* `RelativeHumidity` | ||
* `Speed` | ||
* `Temperature` | ||
* `Torque` | ||
* `Voltage` | ||
* `Volume` | ||
|
||
## Unit Conversions | ||
|
||
Each of these units have an enum of `UnitTypes` that they can be described as, as well as accessed as, via properties. | ||
|
||
For instance the `Temperature` type has properties such as `Fahrenheit`, `Celsius`, `Kelvin`, etc. that allow you to access the unit value, converted to that particular `UnitType`. Consider the following code: | ||
|
||
```csharp | ||
var temp = new Temperature(32, UnitType.Fahrenheit); | ||
Console.WriteLine($"{temp.Celsius:N2}C"); // outputs `0C` | ||
Console.WriteLine($"{temp.Fahrenheit:N2}F"); // outputs `32F` | ||
``` | ||
|
||
The units are all lightweight `struct` types which help to reduce heap allocations (when not boxed by Nullable), and have built in math operator and comparison support so you can perform math operations and comparison such as: | ||
|
||
```csharp | ||
Temperature t1 = new Temperature(1); | ||
Temperature t2 = new Temperature(10); | ||
Assert.That(t1 != t2); | ||
Assert.That((t1 + t2) == new Temperature(11)); | ||
Assert.That((t2 - t1) == new Temperature(9)); | ||
Assert.That(t1 < t2); | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
using System; | ||
using System.Diagnostics.Contracts; | ||
|
||
namespace Meadow.Units | ||
{ | ||
/// <summary> | ||
/// Represents a location on the surface of an ideal Earth (latitude and longitude) | ||
/// </summary> | ||
public record GeoLocation | ||
{ | ||
/// <summary> | ||
/// Idealized earth radius used for internal calculations | ||
/// </summary> | ||
public Length EarthRadius => new Length(6371.01, Length.UnitType.Kilometers); | ||
|
||
/// <summary> | ||
/// The latitude portion of the GeoLocation | ||
/// </summary> | ||
public double Latitude { get; set; } | ||
/// <summary> | ||
/// The Longitude portion of the GeoLocation | ||
/// </summary> | ||
public double Longitude { get; set; } | ||
|
||
/// <summary> | ||
/// Creates a GeoLocation instance | ||
/// </summary> | ||
/// <param name="latitude"></param> | ||
/// <param name="longitude"></param> | ||
public GeoLocation(double latitude, double longitude) | ||
{ | ||
Latitude = latitude; | ||
Longitude = longitude; | ||
} | ||
|
||
[Pure] | ||
private static double DegreesToRadians(double degrees) | ||
{ | ||
return degrees * Math.PI / 180.0; | ||
} | ||
|
||
[Pure] | ||
private static double RadiansToDegrees(double radians) | ||
{ | ||
return radians * 180 / Math.PI; | ||
} | ||
|
||
/// <summary> | ||
/// Calculates the distance to anotehr GeoLocation | ||
/// </summary> | ||
/// <param name="other"></param> | ||
/// <returns></returns> | ||
[Pure] | ||
public Length DistanceTo(GeoLocation other) | ||
{ | ||
var diffLat = DegreesToRadians(other.Latitude - this.Latitude); | ||
var diffLong = DegreesToRadians(other.Longitude - this.Longitude); | ||
|
||
var a = Math.Sin(diffLat / 2) * Math.Sin(diffLat / 2) + | ||
Math.Cos(DegreesToRadians(this.Latitude)) * Math.Cos(DegreesToRadians(other.Latitude)) * | ||
Math.Sin(diffLong / 2) * Math.Sin(diffLong / 2); | ||
var c = 2 * Math.Asin(Math.Min(1, Math.Sqrt(a))); | ||
var d = EarthRadius * c; | ||
|
||
return d; | ||
} | ||
|
||
/// <summary> | ||
/// Calculates the bearing to another GeoLocation | ||
/// </summary> | ||
/// <param name="other"></param> | ||
/// <returns></returns> | ||
[Pure] | ||
public Azimuth BearingTo(GeoLocation other) | ||
{ | ||
var dLon = DegreesToRadians(other.Longitude - this.Longitude); | ||
var dPhi = Math.Log(Math.Tan(DegreesToRadians(other.Latitude) / 2 + Math.PI / 4) / Math.Tan(DegreesToRadians(this.Latitude) / 2 + Math.PI / 4)); | ||
if (Math.Abs(dLon) > Math.PI) | ||
{ | ||
dLon = dLon > 0 ? -(2 * Math.PI - dLon) : (2 * Math.PI + dLon); | ||
} | ||
|
||
return Azimuth.FromRadians(Math.Atan2(dLon, dPhi)); | ||
} | ||
|
||
/// <summary> | ||
/// Creates a new GeoLocation a given bearing and distance from the current GeoLocation | ||
/// </summary> | ||
/// <param name="bearing">Bearing angle to the new location</param> | ||
/// <param name="distance">Distance to the new location</param> | ||
/// <returns></returns> | ||
[Pure] | ||
public GeoLocation Move(Azimuth bearing, Length distance) // double initialBearingRadians, double distanceKilometres) | ||
{ | ||
var distRatio = distance.Meters / EarthRadius.Meters; | ||
var distRatioSine = Math.Sin(distRatio); | ||
var distRatioCosine = Math.Cos(distRatio); | ||
|
||
var startLatRad = DegreesToRadians(this.Latitude); | ||
var startLonRad = DegreesToRadians(this.Longitude); | ||
|
||
var startLatCos = Math.Cos(startLatRad); | ||
var startLatSin = Math.Sin(startLatRad); | ||
|
||
var endLatRads = Math.Asin((startLatSin * distRatioCosine) + (startLatCos * distRatioSine * Math.Cos(bearing.Radians))); | ||
|
||
var endLonRads = startLonRad | ||
+ Math.Atan2( | ||
Math.Sin(bearing.Radians) * distRatioSine * startLatCos, | ||
distRatioCosine - startLatSin * Math.Sin(endLatRads)); | ||
|
||
return new GeoLocation(RadiansToDegrees(endLatRads), RadiansToDegrees(endLonRads)); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.