Functions/GenXdev.Helpers.Physics/Convert-PhysicsUnit.ps1

<#
.SYNOPSIS
Converts a value from one physics unit to another within the same category.

.DESCRIPTION
This function converts physical quantities between compatible units in categories
like length, time, speed, etc. It infers the category from the units and throws
an error for incompatible conversions.

.LICENSE
Copyright (C) 2026 René Vaessen / GenXdev

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.

.PARAMETER Value
The numerical value to convert.

.PARAMETER FromUnit
The unit of the input value.

.PARAMETER ToUnit
The desired output unit.

.EXAMPLE
Convert-PhysicsUnit -Value 100 -FromUnit "meters" -ToUnit "feet"

Converts 100 meters to feet.

.EXAMPLE
Convert-PhysicsUnit 10 "seconds" "minutes"

Converts 10 seconds to minutes using positional parameters.
#>

function Convert-PhysicsUnit {

    [CmdletBinding()]
    [OutputType([double])]

    param(
        ########################################################################
        [parameter(
            Mandatory = $true,
            Position = 0,
            HelpMessage = "The numerical value to convert"
        )]
        [double] $Value,
        ########################################################################
        [parameter(
            Mandatory = $true,
            Position = 1,
            HelpMessage = "The unit of the input value"
        )]
        [ArgumentCompleter({
            param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)

            $unitData = @{
                'meters' = @{ Category = 'Length'; Factor = 1 }
                'kilometers' = @{ Category = 'Length'; Factor = 1000 }
                'centimeters' = @{ Category = 'Length'; Factor = 0.01 }
                'millimeters' = @{ Category = 'Length'; Factor = 0.001 }
                'feet' = @{ Category = 'Length'; Factor = 0.3048 }
                'inches' = @{ Category = 'Length'; Factor = 0.0254 }
                'miles' = @{ Category = 'Length'; Factor = 1609.34 }
                'yards' = @{ Category = 'Length'; Factor = 0.9144 }
                'seconds' = @{ Category = 'Time'; Factor = 1 }
                'minutes' = @{ Category = 'Time'; Factor = 60 }
                'hours' = @{ Category = 'Time'; Factor = 3600 }
                'days' = @{ Category = 'Time'; Factor = 86400 }
                'milliseconds' = @{ Category = 'Time'; Factor = 0.001 }
                'm/s' = @{ Category = 'Speed'; Factor = 1 }
                'km/h' = @{ Category = 'Speed'; Factor = 0.277778 }
                'mph' = @{ Category = 'Speed'; Factor = 0.44704 }
                'ft/s' = @{ Category = 'Speed'; Factor = 0.3048 }
                'kilograms' = @{ Category = 'Mass'; Factor = 1 }
                'grams' = @{ Category = 'Mass'; Factor = 0.001 }
                'pounds' = @{ Category = 'Mass'; Factor = 0.453592 }
                'ounces' = @{ Category = 'Mass'; Factor = 0.0283495 }
                'joules' = @{ Category = 'Energy'; Factor = 1 }
                'calories' = @{ Category = 'Energy'; Factor = 4.184 }
                'kilowatthours' = @{ Category = 'Energy'; Factor = 3600000 }
                'kgm/s' = @{ Category = 'Momentum'; Factor = 1 }
                'm/s²' = @{ Category = 'Acceleration'; Factor = 1 }
                'g' = @{ Category = 'Acceleration'; Factor = 9.81 }
                'newtons' = @{ Category = 'Force'; Factor = 1 }
                'poundforce' = @{ Category = 'Force'; Factor = 4.44822 }
                'hertz' = @{ Category = 'Frequency'; Factor = 1 }
                'kilohertz' = @{ Category = 'Frequency'; Factor = 1000 }
                'megahertz' = @{ Category = 'Frequency'; Factor = 1000000 }
                'radians' = @{ Category = 'Angle'; Factor = 1 }
                'degrees' = @{ Category = 'Angle'; Factor = 0.0174533 } # PI/180
                'kg/m³' = @{ Category = 'Density'; Factor = 1 }
                'cubicmeters' = @{ Category = 'Volume'; Factor = 1 }
                'liters' = @{ Category = 'Volume'; Factor = 0.001 }
                'cubiccentimeters' = @{ Category = 'Volume'; Factor = 0.000001 }
                'squaremeters' = @{ Category = 'Area'; Factor = 1 }
                'squarecentimeters' = @{ Category = 'Area'; Factor = 0.0001 }
                'acres' = @{ Category = 'Area'; Factor = 4046.86 }
                'squarefeet' = @{ Category = 'Area'; Factor = 0.092903 }
            }

            $suggestions = @()
            if ($fakeBoundParameters.ContainsKey('ToUnit')) {
                $otherUnit = $fakeBoundParameters['ToUnit']
                if ($unitData.ContainsKey($otherUnit)) {
                    $category = $unitData[$otherUnit].Category
                    $suggestions = $unitData.Keys | Microsoft.PowerShell.Core\Where-Object { $unitData[$_].Category -eq $category -and $_ -like "*$wordToComplete*" }
                }
            } else {
                $suggestions = $unitData.Keys | Microsoft.PowerShell.Core\Where-Object { $_ -like "*$wordToComplete*" }
            }

            $suggestions | Microsoft.PowerShell.Core\ForEach-Object {
                [System.Management.Automation.CompletionResult]::new("'$($_)'", $_, 'ParameterValue', $_)
            }
        })]
        [string] $FromUnit,
        ########################################################################
        [parameter(
            Mandatory = $true,
            Position = 2,
            HelpMessage = "The desired output unit"
        )]
        [ArgumentCompleter({
            param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)

            $unitData = @{
                'meters' = @{ Category = 'Length'; Factor = 1 }
                'kilometers' = @{ Category = 'Length'; Factor = 1000 }
                'centimeters' = @{ Category = 'Length'; Factor = 0.01 }
                'millimeters' = @{ Category = 'Length'; Factor = 0.001 }
                'feet' = @{ Category = 'Length'; Factor = 0.3048 }
                'inches' = @{ Category = 'Length'; Factor = 0.0254 }
                'miles' = @{ Category = 'Length'; Factor = 1609.34 }
                'yards' = @{ Category = 'Length'; Factor = 0.9144 }
                'seconds' = @{ Category = 'Time'; Factor = 1 }
                'minutes' = @{ Category = 'Time'; Factor = 60 }
                'hours' = @{ Category = 'Time'; Factor = 3600 }
                'days' = @{ Category = 'Time'; Factor = 86400 }
                'milliseconds' = @{ Category = 'Time'; Factor = 0.001 }
                'm/s' = @{ Category = 'Speed'; Factor = 1 }
                'km/h' = @{ Category = 'Speed'; Factor = 0.277778 }
                'mph' = @{ Category = 'Speed'; Factor = 0.44704 }
                'ft/s' = @{ Category = 'Speed'; Factor = 0.3048 }
                'kilograms' = @{ Category = 'Mass'; Factor = 1 }
                'grams' = @{ Category = 'Mass'; Factor = 0.001 }
                'pounds' = @{ Category = 'Mass'; Factor = 0.453592 }
                'ounces' = @{ Category = 'Mass'; Factor = 0.0283495 }
                'joules' = @{ Category = 'Energy'; Factor = 1 }
                'calories' = @{ Category = 'Energy'; Factor = 4.184 }
                'kilowatthours' = @{ Category = 'Energy'; Factor = 3600000 }
                'kgm/s' = @{ Category = 'Momentum'; Factor = 1 }
                'm/s²' = @{ Category = 'Acceleration'; Factor = 1 }
                'g' = @{ Category = 'Acceleration'; Factor = 9.81 }
                'newtons' = @{ Category = 'Force'; Factor = 1 }
                'poundforce' = @{ Category = 'Force'; Factor = 4.44822 }
                'hertz' = @{ Category = 'Frequency'; Factor = 1 }
                'kilohertz' = @{ Category = 'Frequency'; Factor = 1000 }
                'megahertz' = @{ Category = 'Frequency'; Factor = 1000000 }
                'radians' = @{ Category = 'Angle'; Factor = 1 }
                'degrees' = @{ Category = 'Angle'; Factor = 0.0174533 }
                'kg/m³' = @{ Category = 'Density'; Factor = 1 }
                'cubicmeters' = @{ Category = 'Volume'; Factor = 1 }
                'liters' = @{ Category = 'Volume'; Factor = 0.001 }
                'cubiccentimeters' = @{ Category = 'Volume'; Factor = 0.000001 }
                'squaremeters' = @{ Category = 'Area'; Factor = 1 }
                'squarecentimeters' = @{ Category = 'Area'; Factor = 0.0001 }
                'acres' = @{ Category = 'Area'; Factor = 4046.86 }
                'squarefeet' = @{ Category = 'Area'; Factor = 0.092903 }
            }

            $suggestions = @()
            if ($fakeBoundParameters.ContainsKey('FromUnit')) {
                $otherUnit = $fakeBoundParameters['FromUnit']
                if ($unitData.ContainsKey($otherUnit)) {
                    $category = $unitData[$otherUnit].Category
                    $suggestions = $unitData.Keys | Microsoft.PowerShell.Core\Where-Object { $unitData[$_].Category -eq $category -and $_ -like "*$wordToComplete*" }
                }
            } else {
                $suggestions = $unitData.Keys | Microsoft.PowerShell.Core\Where-Object { $_ -like "*$wordToComplete*" }
            }

            $suggestions | Microsoft.PowerShell.Core\ForEach-Object {
                [System.Management.Automation.CompletionResult]::new("'$($_)'", $_, 'ParameterValue', $_)
            }
        })]
        [string] $ToUnit
        ########################################################################
    )

    begin {

        # define unit data with categories and conversion factors to base units
        $unitData = @{
            'meters' = @{ Category = 'Length'; Factor = 1 }
            'kilometers' = @{ Category = 'Length'; Factor = 1000 }
            'centimeters' = @{ Category = 'Length'; Factor = 0.01 }
            'millimeters' = @{ Category = 'Length'; Factor = 0.001 }
            'feet' = @{ Category = 'Length'; Factor = 0.3048 }
            'inches' = @{ Category = 'Length'; Factor = 0.0254 }
            'miles' = @{ Category = 'Length'; Factor = 1609.34 }
            'yards' = @{ Category = 'Length'; Factor = 0.9144 }
            'seconds' = @{ Category = 'Time'; Factor = 1 }
            'minutes' = @{ Category = 'Time'; Factor = 60 }
            'hours' = @{ Category = 'Time'; Factor = 3600 }
            'days' = @{ Category = 'Time'; Factor = 86400 }
            'milliseconds' = @{ Category = 'Time'; Factor = 0.001 }
            'm/s' = @{ Category = 'Speed'; Factor = 1 }
            'km/h' = @{ Category = 'Speed'; Factor = 0.277778 }
            'mph' = @{ Category = 'Speed'; Factor = 0.44704 }
            'ft/s' = @{ Category = 'Speed'; Factor = 0.3048 }
            'kilograms' = @{ Category = 'Mass'; Factor = 1 }
            'grams' = @{ Category = 'Mass'; Factor = 0.001 }
            'pounds' = @{ Category = 'Mass'; Factor = 0.453592 }
            'ounces' = @{ Category = 'Mass'; Factor = 0.0283495 }
            'joules' = @{ Category = 'Energy'; Factor = 1 }
            'calories' = @{ Category = 'Energy'; Factor = 4.184 }
            'kilowatthours' = @{ Category = 'Energy'; Factor = 3600000 }
            'kgm/s' = @{ Category = 'Momentum'; Factor = 1 }
            'm/s²' = @{ Category = 'Acceleration'; Factor = 1 }
            'g' = @{ Category = 'Acceleration'; Factor = 9.81 }
            'newtons' = @{ Category = 'Force'; Factor = 1 }
            'poundforce' = @{ Category = 'Force'; Factor = 4.44822 }
            'hertz' = @{ Category = 'Frequency'; Factor = 1 }
            'kilohertz' = @{ Category = 'Frequency'; Factor = 1000 }
            'megahertz' = @{ Category = 'Frequency'; Factor = 1000000 }
            'radians' = @{ Category = 'Angle'; Factor = 1 }
            'degrees' = @{ Category = 'Angle'; Factor = 0.0174533 }
            'kg/m³' = @{ Category = 'Density'; Factor = 1 }
            'cubicmeters' = @{ Category = 'Volume'; Factor = 1 }
            'liters' = @{ Category = 'Volume'; Factor = 0.001 }
            'cubiccentimeters' = @{ Category = 'Volume'; Factor = 0.000001 }
            'squaremeters' = @{ Category = 'Area'; Factor = 1 }
            'squarecentimeters' = @{ Category = 'Area'; Factor = 0.0001 }
            'acres' = @{ Category = 'Area'; Factor = 4046.86 }
            'squarefeet' = @{ Category = 'Area'; Factor = 0.092903 }
        }

        Microsoft.PowerShell.Utility\Write-Verbose (
            "Converting $Value from $FromUnit to $ToUnit"
        )
    }

    process {

        if (-not $unitData.ContainsKey($FromUnit) -or -not $unitData.ContainsKey($ToUnit)) {
            throw "Unknown unit: $FromUnit or $ToUnit"
        }

        $fromCategory = $unitData[$FromUnit].Category
        $toCategory = $unitData[$ToUnit].Category

        if ($fromCategory -ne $toCategory) {
            throw "Incompatible units: $FromUnit ($fromCategory) and $ToUnit ($toCategory)"
        }

        # convert to base unit
        $baseValue = $Value * $unitData[$FromUnit].Factor

        # convert from base to target
        $result = $baseValue / $unitData[$ToUnit].Factor

        Microsoft.PowerShell.Utility\Write-Verbose (
            "Conversion result: $result $ToUnit"
        )
    }

    end {

        return [Math]::Round($result, 4)
    }
}
################################################################################