Geocoding.psm1

function ConvertFrom-GeoAzureMapsDistanceOutput {
    <#
    .SYNOPSIS
        Convert route output from Azure Maps to uniform output format.
 
    .DESCRIPTION
        Convert route directions output from Azure Maps to the uniform distance
        output format used by Find-GeoCodeDistance.
 
    .PARAMETER Resource
        The raw response object returned by Find-GeoCodeDistanceAzureMaps.
 
    .EXAMPLE
        Convert the output
 
        PS> ConvertFrom-GeoAzureMapsDistanceOutput -Resource $output
    #>


    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', '', Justification = 'The product is called like this.')]

    [CmdLetBinding()]
    [OutputType([Object])]

    Param (
        [Parameter(Mandatory = $true, Position = 1)]
        [Object] $Resource
    )

    Write-Debug "[AzureMaps] convert distance output"

    $summary   = $Resource.routes[0].summary
    $delaySecs = $summary.trafficDelayInSeconds

    $trafficDelay = if ($null -ne $delaySecs -and $delaySecs -gt 0) {
        [TimeSpan]::FromSeconds($delaySecs)
    }
    else {
        $null
    }

    return [PSCustomObject]@{
        "Distance"     = [PSCustomObject]@{
            "Meters" = $summary.lengthInMeters
        }
        "Duration"     = [TimeSpan]::FromSeconds($summary.travelTimeInSeconds)
        "TrafficDelay" = $trafficDelay
    }
}

function ConvertFrom-GeoAzureMapsOutput {
    <#
    .SYNOPSIS
        Convert output from Azure Maps to uniform output format.
 
    .DESCRIPTION
        Convert output from Azure Maps to uniform output format.
 
    .PARAMETER Resource
        The output from Azure Maps
 
    .EXAMPLE
        Convert the output
 
        PS> ConvertFrom-GeoAzureMapsOutput -Resource $output
    #>


    [CmdLetBinding()]
    [OutputType([Object])]

    Param (
        [Parameter(Mandatory = $true, Position = 1)]
        [Object] $Resource
    )

    Write-Debug "[AzureMaps] convert output"

    return [PSCustomObject]@{
        "Coordinates" = [PSCustomObject]@{
            "Latitude"  = $Resource.geometry.coordinates[1]
            "Longitude" = $Resource.geometry.coordinates[0]
        }
        "Address"     = [PSCustomObject]@{
            "Street Address" = if($Resource.properties.address.addressLine){$Resource.properties.address.addressLine}else{$null}
            "Locality"       = if($Resource.properties.address.locality){$Resource.properties.address.locality}else{$null}
            "Region"         = if($Resource.properties.address.adminDistricts){$Resource.properties.address.adminDistricts[0].shortName}else{$null}
            "Postal Code"    = if($Resource.properties.address.postalCode){$Resource.properties.address.postalCode}else{$null}
            "Country"        = if($Resource.properties.address.countryRegion.name){$Resource.properties.address.countryRegion.name}else{$null}
        }
        "Boundingbox" = [PSCustomObject]@{
            "South Latitude" = $Resource.bbox[1]
            "West Longitude" = $Resource.bbox[0]
            "North Latitude" = $Resource.bbox[3]
            "East Longitude" = $Resource.bbox[2]
        }
    }
}


function ConvertFrom-GeoGoogleMapsDistanceOutput {
    <#
    .SYNOPSIS
        Convert distance output from Google Maps to uniform output format.
 
    .DESCRIPTION
        Convert distance matrix output from Google Maps to the uniform distance
        output format used by Find-GeoCodeDistance.
 
    .PARAMETER Resource
        The raw response object returned by Find-GeoCodeDistanceGoogleMaps.
 
    .EXAMPLE
        Convert the output
 
        PS> ConvertFrom-GeoGoogleMapsDistanceOutput -Resource $output
    #>


    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', '', Justification = 'The product is called like this.')]

    [CmdLetBinding()]
    [OutputType([Object])]

    Param (
        [Parameter(Mandatory = $true, Position = 1)]
        [Object] $Resource
    )

    Write-Debug "[GoogleMaps] convert distance output"

    $element  = $Resource.rows[0].elements[0]
    $baseSecs = $element.duration.value

    if ($element.duration_in_traffic) {
        $totalSecs    = $element.duration_in_traffic.value
        # Clamp to zero — negative delay (faster with traffic) is reported as no delay.
        $delaySecs    = [Math]::Max(0, $totalSecs - $baseSecs)
        # Always return a TimeSpan when traffic data was returned, even for zero delay.
        # A null TrafficDelay means "no traffic data", a zero TimeSpan means "no extra delay".
        $trafficDelay = [TimeSpan]::FromSeconds($delaySecs)
    }
    else {
        $totalSecs    = $baseSecs
        $trafficDelay = $null
    }

    return [PSCustomObject]@{
        "Distance"     = [PSCustomObject]@{
            "Meters" = $element.distance.value
        }
        "Duration"     = [TimeSpan]::FromSeconds($totalSecs)
        "TrafficDelay" = $trafficDelay
    }
}

function ConvertFrom-GeoGoogleMapsOutput {
    <#
    .SYNOPSIS
        Convert output from Google Maps to uniform output format.
 
    .DESCRIPTION
        Convert output from Google Maps to uniform output format.
 
    .PARAMETER Resource
        The output from Google Maps
 
    .EXAMPLE
        Convert the output
 
        PS> ConvertFrom-GeoGoogleMapsOutput -Resource $output
    #>


    [CmdLetBinding()]
    [OutputType([Object])]

    Param (
        [Parameter(Mandatory = $true, Position = 1)]
        [Object] $Resource
    )

    Write-Debug "[GoogleMaps] convert output"

    return [PSCustomObject]@{
        "Coordinates" = [PSCustomObject]@{
            "Latitude"  = $Resource.geometry.location.lat
            "Longitude" = $Resource.geometry.location.lng
        }
        "Address"     = [PSCustomObject]@{
            "Street Address" = ($Resource.address_components | Where-Object {$_.types -like "*route*"}).long_name + " " + ($Resource.address_components | Where-Object {$_.types -like "*street_number*"}).long_name
            "Locality"       = ($Resource.address_components | Where-Object {$_.types -like "*locality*"}).long_name
            "Region"         = ($Resource.address_components | Where-Object {$_.types -like "*administrative_area_level_1*"}).long_name
            "Postal Code"    = ($Resource.address_components | Where-Object {$_.types -like "*postal_code*"}).long_name
            "Country"        = ($Resource.address_components | Where-Object {$_.types -like "*country*"}).long_name
        }
        "Boundingbox" = [PSCustomObject]@{
            "South Latitude" = $Resource.geometry.viewport.southwest.lat
            "West Longitude" = $Resource.geometry.viewport.southwest.lng
            "North Latitude" = $Resource.geometry.viewport.northeast.lat
            "East Longitude" = $Resource.geometry.viewport.northeast.lng
        }
    }
}


function ConvertFrom-GeoNominatimOutput {
    <#
    .SYNOPSIS
        Convert output from Open Street Maps to uniform output format.
 
    .DESCRIPTION
        Convert output from Open Street Maps to uniform output format.
 
    .PARAMETER Resource
        The output from Open Street Maps
 
    .EXAMPLE
        Convert the output
 
        PS> ConvertFrom-GeoNominatimOutput -Resource $output
    #>


    [CmdLetBinding()]
    [OutputType([Object])]

    Param (
        [Parameter(Mandatory = $true, Position = 1)]
        [Object] $Resource
    )

    Write-Debug "[OpenStreetMaps] convert output"

    return [PSCustomObject]@{
        "Coordinates" = [PSCustomObject]@{
            "Latitude"  = $Resource.lat
            "Longitude" = $Resource.lon
        }
        "Address"     = [PSCustomObject]@{
            "Street Address" = $Resource.address.road + " " + $Resource.address.house_number
            "Locality"       = $Resource.address.city
            "Region"         = $Resource.address.state
            "Postal Code"    = $Resource.address.postcode
            "Country"        = $Resource.address.country
        }
        "Boundingbox" = [PSCustomObject]@{
            "South Latitude" = $Resource.boundingbox[0]
            "West Longitude" = $Resource.boundingbox[2]
            "North Latitude" = $Resource.boundingbox[1]
            "East Longitude" = $Resource.boundingbox[3]
        }
    }
}


function ConvertFrom-GeoOsrmDistanceOutput {
    <#
    .SYNOPSIS
        Convert route output from OSRM to uniform output format.
 
    .DESCRIPTION
        Convert route output from OSRM (Open Source Routing Machine) to the
        uniform distance output format used by Find-GeoCodeDistance.
 
    .PARAMETER Resource
        The raw response object returned by Find-GeoCodeDistanceOsrm.
 
    .EXAMPLE
        Convert the output
 
        PS> ConvertFrom-GeoOsrmDistanceOutput -Resource $output
    #>


    [CmdLetBinding()]
    [OutputType([Object])]

    Param (
        [Parameter(Mandatory = $true, Position = 1)]
        [Object] $Resource
    )

    Write-Debug "[OSRM] convert distance output"

    $route = $Resource.routes[0]

    return [PSCustomObject]@{
        "Distance"     = [PSCustomObject]@{
            "Meters" = [Math]::Round($route.distance, 0)
        }
        "Duration"     = [TimeSpan]::FromSeconds([Math]::Round($route.duration, 0))
        "TrafficDelay" = $null
    }
}

function Find-GeoCodeDistanceAzureMaps {
    <#
    .SYNOPSIS
        Calculate the route distance between two coordinates using Azure Maps.
 
    .DESCRIPTION
        Uses the Azure Maps Route Directions API to retrieve route distance and
        duration between an origin and a destination. Azure Maps includes live
        traffic data by default for car routing.
 
    .PARAMETER OriginLatitude
        The latitude of the origin point.
 
    .PARAMETER OriginLongitude
        The longitude of the origin point.
 
    .PARAMETER DestinationLatitude
        The latitude of the destination point.
 
    .PARAMETER DestinationLongitude
        The longitude of the destination point.
 
    .PARAMETER ApiKey
        Azure Maps subscription key.
 
    .PARAMETER TravelMode
        The travel mode. Accepts Driving or Walking. Defaults to Driving.
 
    .PARAMETER DepartureTime
        Optional departure time as a DateTime for route planning.
 
    .PARAMETER ArrivalTime
        Optional desired arrival time as a DateTime. Only valid for Driving mode.
 
    .EXAMPLE
        Calculate driving distance
 
        PS> Find-GeoCodeDistanceAzureMaps -OriginLatitude 52.30 -OriginLongitude 4.75 -DestinationLatitude 53.56 -DestinationLongitude 9.92 -ApiKey "KEY"
    #>


    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', '', Justification = 'The product is called like this.')]

    [CmdLetBinding()]
    [OutputType([Object])]

    Param (
        [Parameter(Mandatory = $true, Position = 1)]
        [Double] $OriginLatitude,

        [Parameter(Mandatory = $true, Position = 2)]
        [Double] $OriginLongitude,

        [Parameter(Mandatory = $true, Position = 3)]
        [Double] $DestinationLatitude,

        [Parameter(Mandatory = $true, Position = 4)]
        [Double] $DestinationLongitude,

        [Parameter(Mandatory = $true, Position = 5)]
        [String] $ApiKey,

        [Parameter(Mandatory = $false, Position = 6)]
        [ValidateSet('Driving', 'Walking')]
        [String] $TravelMode = 'Driving',

        [Parameter(Mandatory = $false)]
        [DateTime] $DepartureTime,

        [Parameter(Mandatory = $false)]
        [DateTime] $ArrivalTime
    )

    $mode = switch ($TravelMode) {
        'Driving' { 'car'        }
        'Walking' { 'pedestrian' }
    }

    $uri = "https://atlas.microsoft.com/route/directions/json?api-version=1.0&subscription-key=$ApiKey&query=$OriginLatitude,$OriginLongitude`:$DestinationLatitude,$DestinationLongitude&travelMode=$mode"

    if ($DepartureTime) {
        $formatted = $DepartureTime.ToString("yyyy-MM-ddTHH:mm:sszzz")
        $uri += "&departAt=$([System.Web.HttpUtility]::UrlEncode($formatted))"
    }
    if ($ArrivalTime) {
        $formatted = $ArrivalTime.ToString("yyyy-MM-ddTHH:mm:sszzz")
        $uri += "&arriveAt=$([System.Web.HttpUtility]::UrlEncode($formatted))"
    }

    Write-Debug "[AzureMaps] Call uri: $uri"
    return Invoke-RestMethod -Uri $uri -Method GET
}

function Find-GeoCodeDistanceGoogleMaps {
    <#
    .SYNOPSIS
        Calculate the route distance between two coordinates using Google Maps.
 
    .DESCRIPTION
        Uses the Google Maps Distance Matrix API to retrieve route distance and
        duration between an origin and a destination.
 
    .PARAMETER OriginLatitude
        The latitude of the origin point.
 
    .PARAMETER OriginLongitude
        The longitude of the origin point.
 
    .PARAMETER DestinationLatitude
        The latitude of the destination point.
 
    .PARAMETER DestinationLongitude
        The longitude of the destination point.
 
    .PARAMETER ApiKey
        Google Maps API key.
 
    .PARAMETER TravelMode
        The travel mode. Accepts Driving, Walking or Transit. Defaults to Driving.
 
    .PARAMETER DepartureTime
        Optional departure time as a DateTime. For Driving mode this also enables
        traffic-aware routing.
 
    .PARAMETER ArrivalTime
        Optional arrival time as a DateTime. Only valid for Transit mode.
 
    .PARAMETER TrafficAware
        When set, requests real-time traffic data for Driving mode by setting the
        departure time to now if no explicit DepartureTime was provided.
 
    .EXAMPLE
        Calculate driving distance with traffic
 
        PS> Find-GeoCodeDistanceGoogleMaps -OriginLatitude 52.30 -OriginLongitude 4.75 -DestinationLatitude 53.56 -DestinationLongitude 9.92 -ApiKey "KEY" -TrafficAware
    #>


    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', '', Justification = 'The product is called like this.')]

    [CmdLetBinding()]
    [OutputType([Object])]

    Param (
        [Parameter(Mandatory = $true, Position = 1)]
        [Double] $OriginLatitude,

        [Parameter(Mandatory = $true, Position = 2)]
        [Double] $OriginLongitude,

        [Parameter(Mandatory = $true, Position = 3)]
        [Double] $DestinationLatitude,

        [Parameter(Mandatory = $true, Position = 4)]
        [Double] $DestinationLongitude,

        [Parameter(Mandatory = $true, Position = 5)]
        [String] $ApiKey,

        [Parameter(Mandatory = $false, Position = 6)]
        [ValidateSet('Driving', 'Walking', 'Transit')]
        [String] $TravelMode = 'Driving',

        [Parameter(Mandatory = $false)]
        [DateTime] $DepartureTime,

        [Parameter(Mandatory = $false)]
        [DateTime] $ArrivalTime,

        [Parameter(Mandatory = $false)]
        [Switch] $TrafficAware
    )

    $mode = switch ($TravelMode) {
        'Driving' { 'driving' }
        'Walking' { 'walking' }
        'Transit' { 'transit' }
    }

    $uri = "https://maps.googleapis.com/maps/api/distancematrix/json?origins=$OriginLatitude,$OriginLongitude&destinations=$DestinationLatitude,$DestinationLongitude&mode=$mode&key=$ApiKey"

    if ($TravelMode -eq 'Driving' -and $TrafficAware -and -not $DepartureTime) {
        $uri += "&departure_time=now"
    }
    elseif ($DepartureTime) {
        $epoch = [System.DateTimeOffset]::new($DepartureTime).ToUnixTimeSeconds()
        $uri += "&departure_time=$epoch"
    }
    elseif ($ArrivalTime) {
        $epoch = [System.DateTimeOffset]::new($ArrivalTime).ToUnixTimeSeconds()
        $uri += "&arrival_time=$epoch"
    }

    Write-Debug "[GoogleMaps] Call uri: $uri"
    return Invoke-RestMethod -Uri $uri -Method GET
}

function Find-GeoCodeDistanceOsrm {
    <#
    .SYNOPSIS
        Calculate the route distance between two coordinates using OSRM.
 
    .DESCRIPTION
        Uses the Open Source Routing Machine (OSRM) HTTP API to retrieve route
        distance and duration between an origin and a destination.
 
    .PARAMETER OriginLatitude
        The latitude of the origin point.
 
    .PARAMETER OriginLongitude
        The longitude of the origin point.
 
    .PARAMETER DestinationLatitude
        The latitude of the destination point.
 
    .PARAMETER DestinationLongitude
        The longitude of the destination point.
 
    .PARAMETER TravelMode
        The travel mode. Accepts Driving or Walking. Defaults to Driving.
 
    .PARAMETER Server
        The base URL of the OSRM server. Defaults to the public demo server
        at http://router.project-osrm.org.
 
    .EXAMPLE
        Calculate driving distance
 
        PS> Find-GeoCodeDistanceOsrm -OriginLatitude 52.30 -OriginLongitude 4.75 -DestinationLatitude 53.56 -DestinationLongitude 9.92
    #>


    [CmdLetBinding()]
    [OutputType([Object])]

    Param (
        [Parameter(Mandatory = $true, Position = 1)]
        [Double] $OriginLatitude,

        [Parameter(Mandatory = $true, Position = 2)]
        [Double] $OriginLongitude,

        [Parameter(Mandatory = $true, Position = 3)]
        [Double] $DestinationLatitude,

        [Parameter(Mandatory = $true, Position = 4)]
        [Double] $DestinationLongitude,

        [Parameter(Mandatory = $false, Position = 5)]
        [ValidateSet('Driving', 'Walking')]
        [String] $TravelMode = 'Driving',

        [Parameter(Mandatory = $false, Position = 6)]
        [String] $Server = 'http://router.project-osrm.org'
    )

    $routeProfile = switch ($TravelMode) {
        'Driving' { 'driving' }
        'Walking' { 'foot'    }
    }

    $coordinates = "$OriginLongitude,$OriginLatitude;$DestinationLongitude,$DestinationLatitude"
    $uri = "$($Server.TrimEnd('/'))/route/v1/$routeProfile/$($coordinates)?overview=false"

    Write-Debug "[OSRM] Call uri: $uri"
    return Invoke-RestMethod -Uri $uri -Method GET
}

function Find-GeoCodeLocationAzureMaps {
    <#
    .SYNOPSIS
        Find a geographical location based on a query or coordinates in Azure Maps.
 
    .DESCRIPTION
        Find a geographical location based on a query or coordinates in Azure Maps.
 
    .PARAMETER Query
        A textual query for the location, this is what you would normally enter in the search bar for the map service. Can't be used together with Lat/Long.
 
    .PARAMETER Latitude
        The latitude as a float. Can't be used together with Query.
 
    .PARAMETER Longitude
        The longitude as a float. Can't be used together with Query.
 
    .PARAMETER Apikey
        Apikey from Azure
 
    .PARAMETER Limit
        Limits the amount of results being returned.
 
    .EXAMPLE
        Find based on query
 
        PS> Find-GeoCodeLocationAzureMaps -Query "Microsoft Building 92, NE 36th St, Redmond, WA 98052, United States" -Apikey <YOUR API KEY>
    #>


    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', '', Justification='The product is called like this.')]

    [CmdLetBinding()]
    [OutputType([System.Object[]])]

    Param (
        [Parameter(Mandatory = $true, Position = 1, ParameterSetName = 'Query')]
        [String] $Query,

        [Alias("Lat")]
        [Parameter(Mandatory = $true, Position = 1, ParameterSetName = 'Lon/Lat')]
        [Single] $Latitude,

        [Alias("Lon")]
        [Parameter(Mandatory = $true, Position = 2, ParameterSetName = 'Lon/Lat')]
        [Single] $Longitude,

        [Parameter(Mandatory = $true, Position = 2, ParameterSetName = 'Query')]
        [Parameter(Mandatory = $true, Position = 3, ParameterSetName = 'Lon/Lat')]
        [String] $ApiKey,

        [Parameter(Mandatory = $false, Position = 3, ParameterSetName = 'Query')]
        [Parameter(Mandatory = $false, Position = 4, ParameterSetName = 'Lon/Lat')]
        [Int32] $Limit
    )

    switch($PsCmdlet.ParameterSetName) {
        "Query" {
            Write-Debug "Q"
            $uri = "https://atlas.microsoft.com/geocode?api-version=2025-01-01&query=$([System.Web.HttpUtility]::UrlEncode($Query))&subscription-key=$ApiKey"

            if($Limit) {
                $uri += "&top=$Limit"
            }
        }

        "Lon/Lat" {
            $uri = "https://atlas.microsoft.com/reverseGeocode?api-version=2025-01-01&coordinates=$($Latitude),$($Longitude)&subscription-key=$ApiKey"
        }
    }

    Write-Debug "[AzureMaps] Call uri: $uri"
    return Invoke-RestMethod -Uri $uri -Method GET
}

function Find-GeoCodeLocationGoogleMaps {
    <#
    .SYNOPSIS
        Find a geographical location based on a query or coordinates in Google Maps.
 
    .DESCRIPTION
        Find a geographical location based on a query or coordinates in Google Maps.
 
    .PARAMETER Query
        A textual query for the location, this is what you would normally enter in the search bar for the map service. Can't be used together with Lat/Long.
 
    .PARAMETER Latitude
        The latitude as a float. Can't be used together with Query.
 
    .PARAMETER Longitude
        The longitude as a float. Can't be used together with Query.
 
    .PARAMETER Apikey
        Apikey from Google
 
    .PARAMETER Language
        The language of the returned values can be changed based on the language. Use a country code which is accepted in the header Accept-Language (like "en-US").
 
    .EXAMPLE
        Find based on query
 
        PS> Find-GeoCodeLocationGooleMaps -Query "Microsoft Building 92, NE 36th St, Redmond, WA 98052, United States" -Apikey <YOUR API KEY>
    #>


    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', '', Justification='The product is called like this.')]

    [CmdLetBinding()]
    [OutputType([System.Object[]])]

    Param (
        [Parameter(Mandatory = $true, Position = 1, ParameterSetName = 'Query')]
        [String] $Query,

        [Alias("Lat")]
        [Parameter(Mandatory = $true, Position = 1, ParameterSetName = 'Lon/Lat')]
        [Single] $Latitude,

        [Alias("Lon")]
        [Parameter(Mandatory = $true, Position = 2, ParameterSetName = 'Lon/Lat')]
        [Single] $Longitude,

        [Parameter(Mandatory = $true, Position = 2, ParameterSetName = 'Query')]
        [Parameter(Mandatory = $true, Position = 3, ParameterSetName = 'Lon/Lat')]
        [String] $ApiKey,

        [Parameter(Mandatory = $false, Position = 4, ParameterSetName = 'Query')]
        [Parameter(Mandatory = $false, Position = 5, ParameterSetName = 'Lon/Lat')]
        [String] $Language = "en-US"
    )

    # Create the headers
    $headers = @{
        "accept-language" = $Language
    }

    switch($PsCmdlet.ParameterSetName) {
        "Query" {
            $uri = "https://maps.googleapis.com/maps/api/geocode/json?address=$([System.Web.HttpUtility]::UrlEncode($Query))&key=$ApiKey"
        }

        "Lon/Lat" {
            $uri = "https://maps.googleapis.com/maps/api/geocode/json?latlng=$($Latitude),$($Longitude)&key=$ApiKey"
        }
    }

    Write-Debug "[GoogleMaps] Call uri: $uri"
    return Invoke-RestMethod -Uri $uri -Method GET -Headers $headers
}

function Find-GeoCodeLocationNominatim {
    <#
    .SYNOPSIS
        Find a geographical location based on a query or coordinates in Open Street Maps.
 
    .DESCRIPTION
        Find a geographical location based on a query or coordinates in Open Street Maps.
 
    .PARAMETER Query
        A textual query for the location, this is what you would normally enter in the search bar for the map service. Can't be used together with Lat/Long.
 
    .PARAMETER Latitude
        The latitude as a float. Can't be used together with Query.
 
    .PARAMETER Longitude
        The longitude as a float. Can't be used together with Query.
 
    .PARAMETER DetailedAddress
        Split the address info into seperate attributes in the output.
 
    .PARAMETER Limit
        Limits the amount of results being returned.
 
    .PARAMETER Language
        The language of the returned values can be changed based on the language. Use a country code which is accepted in the header Accept-Language (like "en-US").
 
    .EXAMPLE
        Find based on query
 
        PS> Find-GeoCodeLocationNominatim -Query "Microsoft Building 92, NE 36th St, Redmond, WA 98052, United States"
    #>


    [CmdLetBinding()]
    [OutputType([System.Object[]])]

    Param (
        [Parameter(Mandatory = $true, Position = 1, ParameterSetName = 'Query')]
        [String] $Query,

        [Alias("Lat")]
        [Parameter(Mandatory = $true, Position = 1, ParameterSetName = 'Lon/Lat')]
        [Single] $Latitude,

        [Alias("Lon")]
        [Parameter(Mandatory = $true, Position = 2, ParameterSetName = 'Lon/Lat')]
        [Single] $Longitude,

        [Parameter(Mandatory = $false, Position = 2, ParameterSetName = 'Query')]
        [Parameter(Mandatory = $false, Position = 3, ParameterSetName = 'Lon/Lat')]
        [Switch] $DetailedAddress,

        [Parameter(Mandatory = $false, Position = 3, ParameterSetName = 'Query')]
        [Parameter(Mandatory = $false, Position = 4, ParameterSetName = 'Lon/Lat')]
        [Int32] $Limit,

        [Parameter(Mandatory = $false, Position = 4, ParameterSetName = 'Query')]
        [Parameter(Mandatory = $false, Position = 5, ParameterSetName = 'Lon/Lat')]
        [String] $Language = "en-US"
    )

    # Create the headers
    $headers = @{
        "accept-language" = $Language
    }

    switch($PsCmdlet.ParameterSetName) {
        "Query" {
            $uri = "https://nominatim.openstreetmap.org/search?q=$([System.Web.HttpUtility]::UrlEncode($Query))&format=jsonv2"

            if($Limit) {
                $uri += "&limit=$Limit"
            }
        }

        "Lon/Lat" {
            $uri = "https://nominatim.openstreetmap.org/reverse?lat=$Latitude&lon=$Longitude&format=jsonv2"
        }
    }

    if($DetailedAddress) {
        $uri += "&addressdetails=1"
    }

    Write-Debug "[OpenStreetMaps] Call uri: $uri"
    return Invoke-RestMethod -Uri $uri -Method GET -Headers $headers
}

function Find-GeoCodeDistance {
    <#
    .SYNOPSIS
        Calculate the route distance and travel time between two coordinates.
 
    .DESCRIPTION
        Calculates the driving or walking route distance and estimated travel
        time between an origin and a destination using latitude/longitude
        coordinates. Supports Open Street Maps (via OSRM), Google Maps and
        Azure Maps.
 
        Open Street Maps can be used without an API key. Google Maps and Azure
        Maps require an API key (-Apikey), which only appears as a parameter
        when a paid provider is selected.
 
        Transit mode is only available for Google Maps; providing it for any
        other provider raises an error. The ValidateSet for -TravelMode always
        shows all three options to keep the parameter discoverable, and the
        provider restriction is enforced at runtime.
 
        -TrafficAware only appears as a parameter when -Provider Google or
        -Provider Azure is selected, since those are the only providers that
        support live traffic data.
 
        -OsrmServer only appears when -Provider OSM or -Provider OpenStreetMaps
        is explicitly selected.
 
        -DepartureTime and -ArrivalTime are mutually exclusive parameter sets.
        Arrival time is only supported by Google Maps.
 
        Use -Unit to choose between Metric (meters, default) and Imperial (miles).
        Duration is returned as a TimeSpan, giving access to .TotalSeconds,
        .TotalMinutes, .TotalHours, .Hours, .Minutes, .Seconds and so on.
        TrafficDelay is also a TimeSpan when traffic data is available.
 
    .PARAMETER OriginLatitude
        The latitude of the origin point as a decimal number (e.g. 52.3037).
 
    .PARAMETER OriginLongitude
        The longitude of the origin point as a decimal number (e.g. 4.7500).
 
    .PARAMETER DestinationLatitude
        The latitude of the destination point as a decimal number.
 
    .PARAMETER DestinationLongitude
        The longitude of the destination point as a decimal number.
 
    .PARAMETER Provider
        The routing service to use. Accepts OpenStreetMaps, OSM, GoogleMaps,
        Google, AzureMaps or Azure. Defaults to OSM. The availability of
        Apikey, OsrmServer and TrafficAware all depend on the selected provider.
 
    .PARAMETER TravelMode
        The travel mode: Driving (default), Walking or Transit. Transit is only
        valid for Google Maps and raises an error for any other provider.
 
    .PARAMETER Unit
        Distance unit for the output. Accepts Metric (returns Distance.Meters,
        the default) or Imperial (returns Distance.Miles).
 
    .PARAMETER DepartureTime
        Optional departure time as a DateTime. Cannot be combined with
        -ArrivalTime. Not supported by Open Street Maps. For Google Maps Driving
        mode, providing a departure time also enables traffic-aware results.
 
    .PARAMETER ArrivalTime
        Optional desired arrival time as a DateTime. Cannot be combined with
        -DepartureTime. Only supported by Google Maps Transit mode.
 
    .PARAMETER Apikey
        API key for the selected provider. Only available (and required) when
        -Provider Google, GoogleMaps, Azure or AzureMaps is selected.
 
    .PARAMETER TrafficAware
        Requests real-time traffic data. Only available when -Provider Google or
        -Provider Azure is selected (this is a dynamic parameter). Only valid
        for Driving mode. For Google Maps this sets the departure time to now if
        no explicit -DepartureTime was provided. Azure Maps always includes live
        traffic for car routing regardless of this switch.
 
    .PARAMETER OsrmServer
        Base URL of the OSRM server. Only available when -Provider OSM or
        -Provider OpenStreetMaps is explicitly selected. Defaults to the public
        demo server at http://router.project-osrm.org.
 
    .EXAMPLE
        Calculate driving distance using Open Street Maps (no API key needed)
 
        PS> Find-GeoCodeDistance -OriginLatitude 52.3037 -OriginLongitude 4.7500 -DestinationLatitude 53.5614 -DestinationLongitude 9.9152
 
        Distance : @{Meters=470426}
        Duration : 05:05:48
        TrafficDelay :
 
    .EXAMPLE
        Calculate driving distance with live traffic using Google Maps
 
        PS> Find-GeoCodeDistance -OriginLatitude 52.3037 -OriginLongitude 4.7500 -DestinationLatitude 53.5614 -DestinationLongitude 9.9152 -Provider Google -Apikey "<KEY>" -TrafficAware
 
    .EXAMPLE
        Calculate distance in miles using Open Street Maps
 
        PS> Find-GeoCodeDistance -OriginLatitude 52.3037 -OriginLongitude 4.7500 -DestinationLatitude 53.5614 -DestinationLongitude 9.9152 -Unit Imperial
 
        Distance : @{Miles=292.28}
        Duration : 05:05:48
        TrafficDelay :
 
    .EXAMPLE
        Calculate transit distance using Google Maps with a target arrival time
 
        PS> Find-GeoCodeDistance -OriginLatitude 52.3037 -OriginLongitude 4.7500 -DestinationLatitude 53.5614 -DestinationLongitude 9.9152 -Provider Google -TravelMode Transit -ArrivalTime (Get-Date).AddHours(3) -Apikey "<KEY>"
    #>


    [CmdLetBinding(DefaultParameterSetName = 'Default')]
    [OutputType([System.Object[]])]

    Param (
        # Base parameters — no ParameterSetName so they work in all parameter sets.
        [Parameter(Mandatory = $true)]
        [Double] $OriginLatitude,

        [Parameter(Mandatory = $true)]
        [Double] $OriginLongitude,

        [Parameter(Mandatory = $true)]
        [Double] $DestinationLatitude,

        [Parameter(Mandatory = $true)]
        [Double] $DestinationLongitude,

        [Parameter(Mandatory = $false)]
        [ValidateSet('OpenStreetMaps', 'OSM', 'GoogleMaps', 'Google', 'AzureMaps', 'Azure')]
        [String] $Provider = 'OSM',

        # TravelMode is a static parameter so that help and tab-completion always
        # work. Transit is validated against the provider in Process{}.
        [Parameter(Mandatory = $false)]
        [ValidateSet('Driving', 'Walking', 'Transit')]
        [String] $TravelMode = 'Driving',

        [Parameter(Mandatory = $false)]
        [ValidateSet('Metric', 'Imperial')]
        [String] $Unit = 'Metric',

        # DepartureTime and ArrivalTime are in separate parameter sets so
        # PowerShell itself prevents both from being specified simultaneously.
        [Parameter(Mandatory = $false, ParameterSetName = 'WithDepartureTime')]
        [DateTime] $DepartureTime,

        [Parameter(Mandatory = $false, ParameterSetName = 'WithArrivalTime')]
        [DateTime] $ArrivalTime
    )

    DynamicParam {
        $dictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary

        # -Apikey: required for Google Maps and Azure Maps.
        if ($Provider -in 'GoogleMaps', 'Google', 'AzureMaps', 'Azure') {
            $akAttr = New-Object System.Management.Automation.ParameterAttribute
            $akAttr.Mandatory = $true
            $akCol  = New-Object System.Collections.ObjectModel.Collection[System.Attribute]
            $akCol.Add($akAttr)
            $akParam = New-Object System.Management.Automation.RuntimeDefinedParameter('Apikey', [string], $akCol)
            $dictionary.Add('Apikey', $akParam)
        }

        # -TrafficAware: only available for providers that support traffic data.
        if ($Provider -in 'GoogleMaps', 'Google', 'AzureMaps', 'Azure') {
            $taAttr = New-Object System.Management.Automation.ParameterAttribute
            $taAttr.Mandatory = $false
            $taCol  = New-Object System.Collections.ObjectModel.Collection[System.Attribute]
            $taCol.Add($taAttr)
            $taParam = New-Object System.Management.Automation.RuntimeDefinedParameter('TrafficAware', [switch], $taCol)
            $dictionary.Add('TrafficAware', $taParam)
        }

        # -OsrmServer: only available when OSM provider is explicitly selected.
        if ($Provider -in 'OSM', 'OpenStreetMaps') {
            $osAttr = New-Object System.Management.Automation.ParameterAttribute
            $osAttr.Mandatory = $false
            $osCol  = New-Object System.Collections.ObjectModel.Collection[System.Attribute]
            $osCol.Add($osAttr)
            $osParam = New-Object System.Management.Automation.RuntimeDefinedParameter('OsrmServer', [string], $osCol)
            $dictionary.Add('OsrmServer', $osParam)
        }

        return $dictionary
    }

    Process {
        # Resolve dynamic-parameter values with their defaults.
        $trafficAware = [bool]$PSBoundParameters['TrafficAware']
        $osrmServer   = if ($PSBoundParameters.ContainsKey('OsrmServer')) { $PSBoundParameters['OsrmServer'] } else { 'http://router.project-osrm.org' }

        # Validate logical constraints.
        if ($TravelMode -eq 'Transit' -and $Provider -notin 'GoogleMaps', 'Google') {
            throw "Transit mode is only supported by Google Maps."
        }
        if ($trafficAware -and $TravelMode -ne 'Driving') {
            throw "TrafficAware is only supported for Driving mode."
        }
        if (($PSBoundParameters.ContainsKey('DepartureTime') -or $PSBoundParameters.ContainsKey('ArrivalTime')) -and $Provider -in 'OpenStreetMaps', 'OSM') {
            throw "DepartureTime and ArrivalTime are not supported by Open Street Maps."
        }
        if ($PSBoundParameters.ContainsKey('ArrivalTime') -and $Provider -notin 'GoogleMaps', 'Google') {
            throw "ArrivalTime is only supported by Google Maps."
        }

        # Invoke the appropriate provider and get the normalised result.
        $result = switch ($Provider) {
            { $_ -in 'OpenStreetMaps', 'OSM' } {
                Write-Debug "[OpenStreetMaps] start distance processing"
                Send-THEvent -ModuleName "Geocoding" -EventName "Find-GeoCodeDistance" -PropertiesHash @{ Provider = "OSM" }

                $splat = @{
                    OriginLatitude       = $OriginLatitude
                    OriginLongitude      = $OriginLongitude
                    DestinationLatitude  = $DestinationLatitude
                    DestinationLongitude = $DestinationLongitude
                    TravelMode           = $TravelMode
                    Server               = $osrmServer
                }
                ConvertFrom-GeoOsrmDistanceOutput -Resource (Find-GeoCodeDistanceOsrm @splat)
            }

            { $_ -in 'GoogleMaps', 'Google' } {
                Write-Debug "[GoogleMaps] start distance processing"
                Send-THEvent -ModuleName "Geocoding" -EventName "Find-GeoCodeDistance" -PropertiesHash @{ Provider = "Google" }

                $splat = @{
                    OriginLatitude       = $OriginLatitude
                    OriginLongitude      = $OriginLongitude
                    DestinationLatitude  = $DestinationLatitude
                    DestinationLongitude = $DestinationLongitude
                    ApiKey               = $PSBoundParameters['Apikey']
                    TravelMode           = $TravelMode
                    TrafficAware         = $trafficAware
                }
                if ($PSBoundParameters.ContainsKey('DepartureTime')) { $splat['DepartureTime'] = $DepartureTime }
                if ($PSBoundParameters.ContainsKey('ArrivalTime'))   { $splat['ArrivalTime']   = $ArrivalTime   }
                ConvertFrom-GeoGoogleMapsDistanceOutput -Resource (Find-GeoCodeDistanceGoogleMaps @splat)
            }

            { $_ -in 'AzureMaps', 'Azure' } {
                Write-Debug "[AzureMaps] start distance processing"
                Send-THEvent -ModuleName "Geocoding" -EventName "Find-GeoCodeDistance" -PropertiesHash @{ Provider = "Azure" }

                $splat = @{
                    OriginLatitude       = $OriginLatitude
                    OriginLongitude      = $OriginLongitude
                    DestinationLatitude  = $DestinationLatitude
                    DestinationLongitude = $DestinationLongitude
                    ApiKey               = $PSBoundParameters['Apikey']
                    TravelMode           = $TravelMode
                }
                if ($PSBoundParameters.ContainsKey('DepartureTime')) { $splat['DepartureTime'] = $DepartureTime }
                if ($PSBoundParameters.ContainsKey('ArrivalTime'))   { $splat['ArrivalTime']   = $ArrivalTime   }
                ConvertFrom-GeoAzureMapsDistanceOutput -Resource (Find-GeoCodeDistanceAzureMaps @splat)
            }
        }

        # Build the output object. TrafficDelay is only included when the provider
        # returned actual traffic data — a null value means no traffic data was
        # available (or requested), so the property is omitted entirely to keep
        # the output clean.
        $distanceObj = if ($Unit -eq 'Imperial') {
            [PSCustomObject]@{ "Miles" = [Math]::Round($result.Distance.Meters / 1609.344, 2) }
        }
        else {
            $result.Distance
        }

        if ($null -ne $result.TrafficDelay) {
            return [PSCustomObject]@{
                "Distance"     = $distanceObj
                "Duration"     = $result.Duration
                "TrafficDelay" = $result.TrafficDelay
            }
        }

        return [PSCustomObject]@{
            "Distance" = $distanceObj
            "Duration" = $result.Duration
        }
    }
}

function Find-GeoCodeLocation {
    <#
    .SYNOPSIS
        Find a geographical location based on a query or coordinates.
 
    .DESCRIPTION
        Find a geographical location based on a query or coordinates. It supports multiple providers being: Open Street Maps, Azure Maps and Google Maps.
 
    .PARAMETER Query
        A textual query for the location, this is what you would normally enter in the search bar for the map service. Can't be used together with Lat/Long.
 
    .PARAMETER Latitude
        The latitude as a float. Can't be used together with Query.
 
    .PARAMETER Longitude
        The longitude as a float. Can't be used together with Query.
 
    .PARAMETER Provider
        The service to use to find the location. It supports Open Street Maps (OSM), Azure Maps (Azure) and Google Maps (Google).
        To use Azure and Google an API key is required which needs to be requested via their service. Open Street Maps can be used without an API key.
        Default it will use Open Street Maps
 
    .PARAMETER Apikey
        Required when using Azure or Google. Needs to be entered as a string.
 
    .PARAMETER Limit
        Limits the amount of results being returned.
 
    .PARAMETER Language
        For Open Street Maps and Google the language of the returned values can be changed based on the language. Use a country code which is accepted in the header Accept-Language (like "en-US").
 
    .EXAMPLE
        Use OpenStreetMaps to query and return a single result
 
        PS> Find-GeoCodeLocation -Query "Microsoft Building 92, NE 36th St, Redmond, WA 98052, United States" -Provider OSM -Limit 1 | fl *
 
        Coordinates : @{Latitude=47.64249155; Longitude=-122.13692695171639}
        Address : @{Street Address=Northeast 36th Street 15010; Locality=; Region=Washington; Postal Code=98052; Country=United States}
        Boundingbox : @{South Latitude=47.6413399; West Longitude=-122.1378316; North Latitude=47.6433901; East Longitude=-122.1365074}
 
    .EXAMPLE
        Use Google Maps to query and return a single result
 
        PS> Find-GeoCodeLocation -Query "Microsoft Building 92, NE 36th St, Redmond, WA 98052, United States" -Provider Google -Apikey <YOUR API KEY> -Limit 1 | fl *
 
        Coordinates : @{Latitude=47,6423109; Longitude=-122,1368406}
        Address : @{Street Address=Northeast 36th Street 15010; Locality=Redmond; Region=Washington; Postal Code=System.Object[]; Country=United States}
        Boundingbox : @{South Latitude=47,6410083697085; West Longitude=-122,138480530292; North Latitude=47,6437063302915; East Longitude=-122,135782569708}
 
    .EXAMPLE
        Use Azure Maps to query and return a single result
 
        PS> Find-GeoCodeLocation -Query "Microsoft Building 92, NE 36th St, Redmond, WA 98052, United States" -Provider Azure -Apikey <YOUR API KEY> -Limit 1 | fl *
 
        Coordinates : @{Latitude=47,6423109; Longitude=-122,1368406}
        Address : @{Street Address=Northeast 36th Street 15010; Locality=Redmond; Region=Washington; Postal Code=System.Object[]; Country=United States}
        Boundingbox : @{South Latitude=47,6410083697085; West Longitude=-122,138480530292; North Latitude=47,6437063302915; East Longitude=-122,135782569708}
 
 
    .EXAMPLE
        Use OpenStreetMaps to lookup coordinates and return a single result
 
        PS> Find-GeoCodeLocation -Latitude 38.75408328 -Longitude -78.13476563 -Provider OSM -Limit 1 | fl *
 
        Coordinates : @{Latitude=38.75186724786314; Longitude=-78.13181680294852}
        Address : @{Street Address=Fodderstack Road ; Locality=; Region=Virginia; Postal Code=22747; Country=United States}
        Boundingbox : @{South Latitude=38.7196074; West Longitude=-78.1576132; North Latitude=38.7593864; East Longitude=-78.1236110}
 
    .EXAMPLE
        Use Google Maps to lookup coordinates and return a single result
 
        PS> Find-GeoCodeLocation -Latitude 38.75408328 -Longitude -78.13476563 -Provider Google -Apikey <YOUR API KEY> -Limit 1 | fl *
 
        Coordinates : @{Latitude=38,75408; Longitude=-78,13477}
        Address : @{Street Address= ; Locality=Flint Hill; Region=Virginia; Postal Code=; Country=United States}
        Boundingbox : @{South Latitude=38,7527135197085; West Longitude=-78,1361614802915; North Latitude=38,7554114802915; East Longitude=-78,1334635197085 }
 
    .EXAMPLE
        Use Azure Maps to lookup coordinates and return a single result
 
        PS> Find-GeoCodeLocation -Latitude 38.75408328 -Longitude -78.13476563 -Provider Azure -Apikey <YOUR API KEY> -Limit 1 | fl *
 
        Coordinates : @{Latitude=38,75408; Longitude=-78,13477}
        Address : @{Street Address= ; Locality=Flint Hill; Region=Virginia; Postal Code=; Country=United States}
        Boundingbox : @{South Latitude=38,7527135197085; West Longitude=-78,1361614802915; North Latitude=38,7554114802915; East Longitude=-78,1334635197085 }
 
 
    .NOTES
        Open Street Maps: https://nominatim.org/release-docs/latest/api/Overview/
        Google Maps: https://developers.google.com/maps/documentation/geocoding
        Azure Maps: https://learn.microsoft.com/en-us/azure/azure-maps/about-azure-maps
    #>


    [CmdLetBinding()]
    [OutputType([System.Object[]])]

    Param (
        [Parameter(Mandatory = $true, Position = 1, ParameterSetName = 'Query')]
        [String] $Query,

        [Alias("Lat")]
        [Parameter(Mandatory = $true, Position = 1, ParameterSetName = 'Lon/Lat')]
        [Single] $Latitude,

        [Alias("Lon")]
        [Parameter(Mandatory = $true, Position = 2, ParameterSetName = 'Lon/Lat')]
        [Single] $Longitude,

        [ValidateSet('OpenStreetMaps', 'OSM', 'GoogleMaps', 'Google', "Azure", "AzureMaps")]
        [Parameter(Mandatory = $false, Position = 2, ParameterSetName = 'Query')]
        [Parameter(Mandatory = $false, Position = 3, ParameterSetName = 'Lon/Lat')]
        [String] $Provider = "OpenStreetMaps",

        [Parameter(Mandatory = $false, Position = 3, ParameterSetName = 'Query')]
        [Parameter(Mandatory = $false, Position = 4, ParameterSetName = 'Lon/Lat')]
        [Int32] $Limit,

        [Parameter(Mandatory = $false, Position = 4, ParameterSetName = 'Query')]
        [Parameter(Mandatory = $false, Position = 5, ParameterSetName = 'Lon/Lat')]
        [String] $Language = "en-US"
    )

    DynamicParam {
        if($Provider -in 'GoogleMaps', 'Google', "Azure", "AzureMaps") {
            $attribute = New-Object System.Management.Automation.ParameterAttribute
            $attribute.Mandatory = $true

            $collection = New-Object System.Collections.ObjectModel.Collection[System.Attribute]
            $collection.Add($attribute)

            $param = New-Object System.Management.Automation.RuntimeDefinedParameter('Apikey', [string], $collection)
            $dictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary
            $dictionary.Add('Apikey', $param)

            return $dictionary
        }
    }

    Process {
        # do actions for the right provider
        switch ($Provider) {
            { $_ -in "OpenStreetMaps", "OSM" } {
                Write-Debug "[OpenStreetMaps] start processing"
                Send-THEvent -ModuleName "Geocoding" -EventName "Find-GeoCodeLocation" -PropertiesHash @{Provider = "OSM" }

                # Create the parameters
                $splat = $PSBoundParameters
                $null = $splat.Remove("Provider")
                $splat.add("DetailedAddress",$true)

                # Query the provider for the results
                $res = Find-GeoCodeLocationNominatim @splat

                # Format the results in a uniform format
                $res = @($res | ForEach-Object {ConvertFrom-GeoNominatimOutput -Resource $_})

                # Return result
                return $res
            }

            { $_ -in "AzureMaps", "Azure" } {
                Write-Debug "[AzureMaps] start processing"
                Send-THEvent -ModuleName "Geocoding" -EventName "Find-GeoCodeLocation" -PropertiesHash @{Provider = "Azure" }

                # Create the parameters
                $splat = $PSBoundParameters
                $null = $splat.Remove("Provider")
                $null = $splat.Remove("Language")

                # Query the provider for the results
                $res = (Find-GeoCodeLocationAzureMaps @splat).features

                # Format the results in a uniform format
                $res = @($res | ForEach-Object {ConvertFrom-GeoAzureMapsOutput -Resource $_})

                # Return result
                return $res
            }

            { $_ -in "GoogleMaps", "Google" } {
                Write-Debug "[GoogleMaps] start processing"
                Send-THEvent -ModuleName "Geocoding" -EventName "Find-GeoCodeLocation" -PropertiesHash @{Provider = "Google" }

                # Create the parameters
                $splat = $PSBoundParameters
                $null = $splat.Remove("Provider")
                $null = $splat.Remove("Limit")

                # Query the provider for the results
                $res = (Find-GeoCodeLocationGoogleMaps @splat).results

                if($Limit) {
                    $res = $res | Select-Object -First $Limit
                }

                # Format the results in a uniform format
                $res = @($res | ForEach-Object {ConvertFrom-GeoGoogleMapsOutput -Resource $_})

                # Return result
                return $res
            }
        }
    }
}

# Create env variables
$Env:GEOCODING_TELEMETRY_OPTIN = (-not $Evn:POWERSHELL_TELEMETRY_OPTOUT) # use the invert of default powershell telemetry setting

# Set up the telemetry
Initialize-THTelemetry -ModuleName "Geocoding"
Set-THTelemetryConfiguration -ModuleName "Geocoding" -OptInVariableName "GEOCODING_TELEMETRY_OPTIN" -StripPersonallyIdentifiableInformation $true -Confirm:$false
Add-THAppInsightsConnectionString -ModuleName "Geocoding" -ConnectionString "InstrumentationKey=df9757a1-873b-41c6-b4a2-2b93d15c9fb1;IngestionEndpoint=https://westeurope-5.in.applicationinsights.azure.com/;LiveEndpoint=https://westeurope.livediagnostics.monitor.azure.com/"

# Create a message about the telemetry
Write-Information ("Telemetry for Geocoding module is $(if([string] $Env:GEOCODING_TELEMETRY_OPTIN -in ("no","false","0")){"NOT "})enabled. Change the behavior by setting the value of "+ '$Env:GEOCODING_TELEMETRY_OPTIN') -InformationAction Continue

# Send a metric for the installation of the module
Send-THEvent -ModuleName "Geocoding" -EventName "Import Module Geocoding"