Geocoding.psm1
function ConvertFrom-GeoBingMapsOutput { <# .SYNOPSIS Convert output from Bing Maps to uniform output format. .DESCRIPTION Convert output from Bing Maps to uniform output format. .PARAMETER Resource The output from Bing Maps .EXAMPLE Convert the output PS> ConvertFrom-GeoBingMapsOutput -Resource $output #> [CmdLetBinding()] [OutputType([Object])] Param ( [Parameter(Mandatory = $true, Position = 1)] [Object] $Resource ) Write-Debug "[BingMaps] convert output" return [PSCustomObject]@{ "Coordinates" = [PSCustomObject]@{ "Latitude" = $Resource.point.coordinates[0] "Longitude" = $Resource.point.coordinates[1] } "Address" = [PSCustomObject]@{ "Street Address" = $Resource.address.addressLine "Locality" = $Resource.address.locality "Region" = $Resource.address.adminDistrict "Postal Code" = $Resource.address.postalCode "Country" = $Resource.address.countryRegion } "Boundingbox" = [PSCustomObject]@{ "South Latitude" = $Resource.bbox[0] "West Longitude" = $Resource.bbox[1] "North Latitude" = $Resource.bbox[2] "East Longitude" = $Resource.bbox[3] } } } 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 Find-GeoCodeLocationBingMaps { <# .SYNOPSIS Find a geographical location based on a query or coordinates in Bing Maps. .DESCRIPTION Find a geographical location based on a query or coordinates in Bing 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 Bing .PARAMETER Limit Limits the amount of results being returned. .EXAMPLE Find based on query PS> Find-GeoCodeLocationBingMaps -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 = "http://dev.virtualearth.net/REST/v1/Locations/$([System.Web.HttpUtility]::UrlEncode($Query))?o=json&key=$ApiKey" if($Limit) { $uri += "&maxResults=$Limit" } } "Lon/Lat" { $uri = "http://dev.virtualearth.net/REST/v1/Locations/$($Latitude),$($Longitude)?o=json&key=$ApiKey" } } Write-Debug "[BingMaps] Call uri: $uri" return Invoke-RestMethod -Uri $uri -Method GET -RetryIntervalSec 1 -MaximumRetryCount 5 } 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 -RetryIntervalSec 1 -MaximumRetryCount 5 -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 -RetryIntervalSec 1 -MaximumRetryCount 5 -Headers $headers } 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, Bing 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), Bing Maps (Bing) and Google Maps (Google). To use Bing 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 Google or Bing. 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 Bing Maps to query and return a single result PS> Find-GeoCodeLocation -Query "Microsoft Building 92, NE 36th St, Redmond, WA 98052, United States" -Provider Bing -Apikey <YOUR API KEY> -Limit 1 | fl * Coordinates : @{Latitude=47,642428; Longitude=-122,05937604} Address : @{Street Address=NE 36th St; Locality=Redmond; Region=WA; Postal Code=98074; Country=United States} Boundingbox : @{South Latitude=47,6385652824306; West Longitude=-122,06701963172; North Latitude=47,646290717572; East Longitude=-122,051732453158} .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 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 Bing Maps to lookup coordinates and return a single result PS> Find-GeoCodeLocation -Latitude 38.75408328 -Longitude -78.13476563 -Provider Bing -Apikey <YOUR API KEY> -Limit 1 | fl * Coordinates : @{Latitude=38,75408173; Longitude=-78,13477325} Address : @{Street Address=; Locality=Hampton; Region=VA; Postal Code=22747; Country=United States} Boundingbox : @{South Latitude=38,7502190085035; West Longitude=-78,1413771887125; North Latitude=38,7579444436449; East Longitude=-78,1281693200765 } .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 } .NOTES Open Street Maps: https://nominatim.org/release-docs/latest/api/Overview/ Bing Maps: https://learn.microsoft.com/en-us/bingmaps/rest-services/locations/ Google Maps: https://developers.google.com/maps/documentation/geocoding #> [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', 'BingMaps', 'Bing', 'GoogleMaps', 'Google')] [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 'BingMaps', 'Bing', 'GoogleMaps', 'Google') { $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 if($Limit) { $res = $res | Select-Object -First $Limit } # Format the results in a uniform format $res = @($res | ForEach-Object {ConvertFrom-GeoNominatimOutput -Resource $_}) # Return result return $res } { $_ -in "BingMaps", "Bing" } { Write-Debug "[BingMaps] start processing" Send-THEvent -ModuleName "Geocoding" -EventName "Find-GeoCodeLocation" -PropertiesHash @{Provider = "Bing" } # Create the parameters $splat = $PSBoundParameters $null = $splat.Remove("Provider") $null = $splat.Remove("Language") # Query the provider for the results $res = (Find-GeoCodeLocationBingMaps @splat).resourceSets.resources if($Limit) { $res = $res | Select-Object -First $Limit } # Format the results in a uniform format $res = @($res | ForEach-Object {ConvertFrom-GeoBingMapsOutput -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" |