Google Geocode API with Powershell

I have a work requirement to look up zip codes based on incomplete addresses (street, city, state, etc) – this could be accomplished by going into Google Maps and plugging in the partial address which will then give you the completed address with the zip code. But what if you had a list of hundreds of incomplete addresses that you need the zip code for?

Below is a demonstration of how to leverage Google’s geocode API using Powershell to perform the zip code lookup.

First off, Google lets you make a handful of calls to their APIs for free, anything over that handful will require an API key. If you get an API key, it is free as long as you stay under a certain limit of calls made per day. If you go over that limit, then you will have to create a Google Developer account and pay for the service as you go. I went ahead by creating a free API key as I do not plan on making more than the limited calls per day. I suggest you look at the API limits and read their documentation as it is very informative and overall beneficial even if you do not end up creating an API key for yourself.

The API call works by using the following URL in your browser – this is Google’s example:
https://maps.googleapis.com/maps/api/geocode/json?address=1600+Amphitheatre+Parkway,+Mountain+View,+CA&key=YOUR_API_KEY

The result of the API call, if it is legitimate and well formed, will return output in JSON format. In Powershell, we have to convert this JSON format:

Invoke-WebRequest $APICall | ConvertFrom-Json

If you set the above Invoke-WebRequest (alias is wget) results to a variable, that variable will now hold the entire read-able json file in a format that can be manipulated by Powershell.

#Convert JSON to read-able format
$APIResults = Invoke-WebRequest $APICall | ConvertFrom-Json 

#Select the portion of the json file that holds the address results
$APIResults | Select Results

#Return only the results without an output header
$APIResults | Select Results).Results 

#From the Results output, get only the formatted_address output and print without the header
$APIResults | Select Results | Select formatted_address).formatted_address

 

<#
PS H:\Code> h

  Id CommandLine
  -- -----------
   1 $APICall = "5XX6 TUJXXXA BLVD@NORTH XXXXX%CA"
   2 cls
   3 .\FindZipCodeBulk.ps1
   4 cls
   5 $APICall
   6 cls
   7 $APICall = "https://maps.googleapis.com/maps/api/geocode/json?address=5XX6 TUJXXXA BLVD@NORTH XXXXX%CA&key=XXXXXXXXXXXXXXXXXXXXXXX"
   8 Invoke-WebRequest $APICall | ConvertFrom-Json
   9 Invoke-WebRequest $APICall | ConvertFrom-Json | Select Results
  10 (Invoke-WebRequest $APICall | ConvertFrom-Json | Select Results).Results
  11 (Invoke-WebRequest $APICall | ConvertFrom-Json | Select Results).Results | Select Formatted_Address
  12 ((Invoke-WebRequest $APICall | ConvertFrom-Json | Select Results).Results | Select Formatted_Address).Formatted_Address
  13 dfs
#>

PS H:\Code> $APICall = "https://maps.googleapis.com/maps/api/geocode/json?address=5XX6+TUJXXGA+BLXD,XXXX+HOLXXXOD,+CA&key=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
PS H:\Code> Invoke-WebRequest $APICall | ConvertFrom-Json

results                                                                                                                                                                                                 status
-------                                                                                                                                                                                                 ------
{@{address_components=System.Object[]; formatted_address=5XX6 XujXXga Ave, XXXX HolXXX, XX 91601, USA; geometry=; partial_match=True; place_id=XXXXXXXXXXXXXXXXX; types=System.Object[]}} OK


PS H:\Code> Invoke-WebRequest $APICall | ConvertFrom-Json | Select Results

results
-------
{@{address_components=System.Object[]; formatted_address=5XX6 XujXXga Ave, XXXX HolXXX, XX 91601, USA; geometry=; partial_match=True; place_id=XXXXXXX; types=System.Object[]}}


PS H:\Code> (Invoke-WebRequest $APICall | ConvertFrom-Json | Select Results).Results


address_components : {@{long_name=XXX; short_name=XXX; types=System.Object[]}, @{long_name=XXXX Avenue; short_name=XXXX Ave; types=System.Object[]}, @{long_name=XXX XXX; short_name=NoHo; types=System.Object[]}, @{long_name=Los Angeles; short_name=Los Angeles; types=System.Object[]}...}
formatted_address  : 5XX6 XujXXga Ave, XXXX HolXXX, XX 91601
geometry           : @{bounds=; location=; location_type=ROOFTOP; viewport=}
partial_match      : True
place_id           : XXXXXXX
types              : {premise}



PS H:\Code> (Invoke-WebRequest $APICall | ConvertFrom-Json | Select Results).Results | Select Formatted_Address

formatted_address
-----------------
5XX6 XujXXga Ave, XXXX HolXXX, XX 91601


PS H:\Code> ((Invoke-WebRequest $APICall | ConvertFrom-Json | Select Results).Results | Select Formatted_Address).Formatted_Address
5XX6 XujXXga Ave, XXXX HolXXX, XX 91601

Great, now we know how to call the API, get data from it, convert the data to a useable format and extract the exact data we want from it – but how do we form the URL from just a plain address automatically?

PS H:\Code> .\FindZipCode.ps1 "XXXX XXXXX BLVD@XXXXX HOLLYWOOD%CA"
5XX6 XujXXga Ave, XXXX HolXXX, XX
XXX+XXXXX+BLVD,+XXXXX+HOLLYWOOD,+CA
https://maps.googleapis.com/maps/api/geocode/json?address=XXX+XXXXX+BLVD,+XXXX+HOLLYWOOD,+CA&key=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
5XX6 XujXXga Ave, XXXX HolXXX, XX 91601
PS H:\Code>

I took the original address and added some symbols to be utilized as markers to easily split the strings and extract the values I want into separate variables. After that, I can use the individual variables and concatenate them into a single string to be used in the API call itself.

param (
    [Parameter(Mandatory=$true)]
    [string] $Address
    )


#Here I am setting my API key as a global variable
$Global:APIKey = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"

#These two variables are the front and rear parts of the API URL we need to form
$Global:APILink1 = "https://maps.googleapis.com/maps/api/geocode/json?address="
$Global:APILink2 = ("&key=" + $APIKey)

#This function actually performs the wget to Google's API
#The function needs a parameter when invokved
function PerformAPICall ($FormedAPIAddress)
{   #This forms the API URL and stores it in a variable
    $APICall = ($APILink1 + $FormedAPIAddress + $APILink2)

    #Attempt to put the input string into the formed address portion of the API URL
    try
    {   #Extract Zip code portion from results of APICall
        $Zip = (((Invoke-WebRequest $APICall | ConvertFrom-Json | Select Results).Results | Select formatted_address).formatted_address -Split ("$State"))

        #Format results to extract only zip code
        $Zip = $Zip[1]
        $Zip = $Zip -Split (",")
        $Zip = $Zip[0]
        $Zip = $Zip.TrimStart()
        $Zip = $Zip.TrimEnd()

        #If contents of $Zip code is not empty
        #Output results
        if ($Zip)
        {
            $ExtractedAddress = ($Street + ", " + $City + "," + " " + $State + " " + $Zip)
            Write-Host $ExtractedAddress       
        }

        #If contents of $Zip is empty
        #Output error message
        else
        {
            Write-Host "$FullAPIAddress - Error on this record! - $APICall <==== Control+Left Click To View" -ForegroundColor Red
        }

    }
    #Catch condition if API call to Google fails
    catch
    {
        Write-Host "Error occurred! - " $APICall " <==== Control+Left Click To View" -ForegroundColor Red
    }
}

#Attempt to convert string received during script execution into required API URL
try 
{   #The URL must be edited to include "*@*" and "*%*"
    #This helps to split the string accordingly, any symbol could be used if desired
    #just change the symbols accordingly below
    if ($Address -like "*@*" -or $Address -like "*%*")
    {
        #Extract Street only and store into variable
        $Street = $Address -Split "@"
        $Street = $Street[0]

        #Extract City only and store into variable
        $City = $Address -Split "@"
        $City = $City -Split "%"
        $City = $City[1]

        #Extract State only and store into variable
        $State = $Address -Split "@"
        $State = $State -Split "%"
        $State = $State[2]

        #Store Street, City, and State into a single variable
        $ExtractedAddress = ($Street + $City + ", " + $State)

        #Store Street, City, and State into a single variable with necessary + symbols as required by API URL call
        $FormedAPIAddress = (($Street -Replace " ", "+") + ",+" + ($City -Replace " ", "+") + ",+" + ($State))

        #Store the first part of the API URL, the actual address, and the end part of the URL into one string variable
        $APICall = ($APILink1 + $FormedAPIAddress + $APILink2)

        #Output results
        #These are not actually needed but show the progression in each step leading up to the API call
        Write-Host $ExtractedAddress
        Write-Host $FormedAPIAddress
        Write-Host $APICall

        #Call function that actually performs API call
        PerformAPICall $FormedAPIAddress
    }
    #Condition to handle if string received during execution does not have the necessary "*@*" and "*%*"
    else
    {
        Write-Host "Address is not formatted correctly!" -ForegroundColor Red
    }
}
#Condition to handle of try condition fails
catch 
{
    Write-Host "Address not provided or incomplete!" -ForegroundColor Red
}

Now we can perform API calls on a single address but what about the earlier scenario where we have hundreds of addresses to lookup?

PS H:\Code> .\FindZipCodeBulk.ps1
XXXX NORTH HOLLYWOOD, CA 91601
XXXX LOS ANGELES, CA 90036
XXXX NEWARK, NJ 07114
XXXX FONTANA, CA 92335
XXXX SUITE 101, FONTANA, CA 92335
XXXX SUITE 105, GLENDORA, CA 91740
XXXX SUITE N, HARBOR CITY, CA 90710
XXXX X LOS ANGELES, CA 90029
XXXX PANORAMA CITY, CA 91402
XXXX PASADENA, CA 91101
XXXX, SEATTLE, WA 98103
XXXX, BIRMINGHAM, AL 35205
XXXX S., BIRMINGHAM, AL 35222
XXXX , BIRMINGHAM, AL 35233
5TH+AVE.+S.,XXXXX,+XXX - Error on this record! - https://maps.googleapis.com/maps/api/geocode/json?address=XXXX+AVE.+S.,XXXXX,+AL&key=XXXXXXXXXXXXXXXXXXXXXXXXXXXXX <==== Control+Left Click To View
XXXX WASHINGTON DC, DC 20045
XXXX CANTON, GA 30114
XXXX BUS, GA 31901

This version of the script works the same way as the first one, the only difference is that I have plugged in my hundreds of addresses in the required format into a text file. This script then reads that text file and does a “for each” loop performing the same logic from the first script on each line of address within the text file.

#Global variables as they need to move to difference functions and contain their content at a wider scope
$Global:APIKey = "XXXXXXXXXXXXXXXXXXXXXXXXXXXX"
$Global:APILink1 = "https://maps.googleapis.com/maps/api/geocode/json?address="
$Global:APILink2 = ("&key=" + $APIKey)
$Global:FullAPIAddress
$Global:APICall

#This function actually performs the wget to Google's API
#The function needs a parameter when invokved
function PerformAPICall ($FormedAPIAddress)
{   #This forms the API URL and stores it in a variable
    $APICall = ($APILink1 + $FormedAPIAddress + $APILink2)

    #Attempt to put the input string into the formed address portion of the API URL
    try
    {   #Extract Zip code portion from results of APICall
        $Zip = (((Invoke-WebRequest $APICall | ConvertFrom-Json | Select Results).Results | Select formatted_address).formatted_address -Split ("$State"))

        #Format results to extract only zip code
        $Zip = $Zip[1]
        $Zip = $Zip -Split (",")
        $Zip = $Zip[0]
        $Zip = $Zip.TrimStart()
        $Zip = $Zip.TrimEnd()

        #If contents of $Zip code is not empty
        #Output results
        if ($Zip)
        {
            $ExtractedAddress = ($Street + ", " + $City + "," + " " + $State + " " + $Zip)
            Write-Host $ExtractedAddress       
        }

        #If contents of $Zip is empty
        #Output error message
        else
        {
            Write-Host "$FullAPIAddress - Error on this record! - $APICall <==== Control+Left Click To View" -ForegroundColor Red
        }

    }
    #Catch condition if API call to Google fails
    catch
    {
        Write-Host "Error occurred! - " $APICall " <==== Control+Left Click To View" -ForegroundColor Red
    }
}

#Checks if the addressList.txt file exists
if (Test-Path .\addressList.txt)
{
    #If it does exist, it will start the foreach loop logic
    foreach ($Line in (gc .\addressList.txt))
    {
        #Take each line in text file and set it equal to $Address variable
        $Address = $Line
        #Take the end of the address and set it equal to $State variable(state abbreviations are usually stored at the end of an address)
        $State = $Address[($Address.Length -2)] + $Address[($Address.Length -1)]

        #Extract Street only and store into variable
        $Street = $Address -Split ("@")
        $Street = $Street[0]

        #Extract City only and store into variable
        $City = $Address -Split ("@")
        $City = $City[1]
        $City = $City -Split ("%")
        $City = $City[0]

        #Remove spaces from front and end of each string
        $Street = $Street.TrimEnd()
        $City = $City.TrimStart()
        $City = $City.TrimEnd()
        $State = $State.TrimStart()

        #Form the API URL string
        $APIStreet = ($Street -Replace " ", "+")
        $APICity = ($City -Replace " ", "+")
        $APIState = ("+" + $State)

        #Concatenate the string together to form the API call
        $FullAPIAddress = ($APIStreet + "," + $APICity + "," + $APIState)
        
        #Try to cal lthe function that performs the API call
        try
        {
            PerformAPICall $FullAPIAddress
        }

        #If the call is executed with a bad record that is not well-formed, then below error will output
        Catch
        {
            Write-Host "$Line - Error on this record! - $APICall <==== Control+Left Click To View" -ForegroundColor Red
        }
    }
}

#Condition to handle if there is not an input file holding list of addresses
else 
{
    Write-Host "addressList.txt file not found!" -ForegroundColor Red
}

Please note, I had to “XXXX” out a lot of content due to security reasons, but the script itself is unaffected. Please comment if you have any questions.

Leave a Reply

avatar
  Subscribe  
Notify of